3 Ways to Return All Tables WITHOUT a Primary Key in SQL Server

If you ever need to find out whether a database has any tables that don’t have a primary key, you can run any of the following scripts in SQL Server to return just those tables.

All of these scripts take advantage of the OBJECTPROPERTY() function. This function accepts a TableHasPrimaryKey argument that you can check for a value of 0. If it’s 0, the table doesn’t have a primary key. If it’s 1 it does. Therefore, you can also use this function to return all tables with a primary key.

These scripts simply return the name of the table and its schema, but you can always modify them to return more columns.

Option 1 – OBJECTPROPERTY() with sys.tables

The sys.tables system view is probably the most obvious place to start. This view returns a row for each user table, and when we use OBJECTPROPERTY() to filter the results based on the TableHasPrimaryKey property being 0, we get just those tables without a primary key.

USE Test;
SELECT 
  SCHEMA_NAME(schema_id) AS [Schema], 
  name AS [Table]
FROM sys.tables
WHERE OBJECTPROPERTY(object_id, 'TableHasPrimaryKey') = 0
ORDER BY [Schema], [Table];

Result:

Changed database context to 'Test'.
+----------+--------------------+
| Schema   | Table              |
|----------+--------------------|
| dbo      | Datetime2Test      |
| dbo      | Datetime2Test2     |
| dbo      | DatetimeoffsetTest |
| dbo      | Individual         |
| dbo      | Occupation         |
| dbo      | Team               |
| dbo      | TimeTest           |
+----------+--------------------+
(7 rows affected)

In this case, my current database is a test database with a bunch of tables without primary keys.

If I run the same statement on another database, I get no results:

USE Music;
SELECT 
  SCHEMA_NAME(schema_id) AS [Schema], 
  name AS [Table]
FROM sys.tables
WHERE OBJECTPROPERTY(object_id, 'TableHasPrimaryKey') = 0
ORDER BY [Schema], [Table];

Result:

Changed database context to 'Music'.
(0 rows affected)

Option 2 – OBJECTPROPERTY() with INFORMATION_SCHEMA.TABLES

This example is similar to the previous one, except this time I’m querying the INFORMATION_SCHEMA.TABLES view. The information schema views included in SQL Server comply with the ISO standard definition for the INFORMATION_SCHEMA.

USE Test;
SELECT 
  TABLE_SCHEMA,
  TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE OBJECTPROPERTY(OBJECT_ID(CONCAT(TABLE_SCHEMA, '.', TABLE_NAME)),'TableHasPrimaryKey') = 0 AND
TABLE_TYPE='BASE TABLE'
ORDER BY TABLE_SCHEMA, TABLE_NAME;

Result:

Changed database context to 'Test'.
+----------------+--------------------+
| TABLE_SCHEMA   | TABLE_NAME         |
|----------------+--------------------|
| dbo            | Datetime2Test      |
| dbo            | Datetime2Test2     |
| dbo            | DatetimeoffsetTest |
| dbo            | Individual         |
| dbo            | Occupation         |
| dbo            | Team               |
| dbo            | TimeTest           |
+----------------+--------------------+
(7 rows affected)

Option 3 – OBJECTPROPERTY() with sys.objects

In this example, I query the sys.objects view. This is a more general view when compared to the previous two, and it returns information about schema-scoped objects (not just tables). Because of this, we need to filter the results using type = 'U'. The U here stands for user-defined table.

Again, we can use the OBJECTPROPERTY() function to filter the results to just those tables that don’t have a primary key.

USE Test;
SELECT 
  SCHEMA_NAME(schema_id) AS [Schema],
  name AS [Table]
FROM sys.objects 
WHERE type = 'U'
AND OBJECTPROPERTY(OBJECT_ID(CONCAT(SCHEMA_NAME(schema_id), '.', name)), 'TableHasPrimaryKey') = 0
ORDER BY [Schema], [Table]

Result:

Changed database context to 'Test'.
+----------+--------------------+
| Schema   | Table              |
|----------+--------------------|
| dbo      | Datetime2Test      |
| dbo      | Datetime2Test2     |
| dbo      | DatetimeoffsetTest |
| dbo      | Individual         |
| dbo      | Occupation         |
| dbo      | Team               |
| dbo      | TimeTest           |
+----------+--------------------+
(7 rows affected)

We could alternatively filter it by type_desc = 'USER_TABLE', which would produce the same result.

USE Test;
SELECT 
  SCHEMA_NAME(schema_id) AS [Schema],
  name AS [Table]
FROM sys.objects 
WHERE type_desc = 'USER_TABLE'
AND OBJECTPROPERTY(OBJECT_ID(CONCAT(SCHEMA_NAME(schema_id), '.', name)), 'TableHasPrimaryKey') = 0
ORDER BY [Schema], [Table]

Result:

Changed database context to 'Test'.
+----------+--------------------+
| Schema   | Table              |
|----------+--------------------|
| dbo      | Datetime2Test      |
| dbo      | Datetime2Test2     |
| dbo      | DatetimeoffsetTest |
| dbo      | Individual         |
| dbo      | Occupation         |
| dbo      | Team               |
| dbo      | TimeTest           |
+----------+--------------------+
(7 rows affected)