Find Out if a CHECK Constraint is Column-Level or Table-Level in SQL Server (T-SQL Examples)

When you create a CHECK constraint in SQL Server, you might not even think about whether it’s a table-level constraint or a column-level constraint.

A table-level CHECK constraint applies to the table, whereas a column-level constraint applies to a specific column. With a table-level CHECK constraint, it’s the row that is checked when it checks the data. With a column-level CHECK constraint, it’s the specific column that is checked.

Generally you’ll know whether or not the constraint you’re creating is a table-level or column-level constraint by the definition you give it. If only one column is being checked in the expression, it will be a column-level constraint. Otherwise it will be a table-level constraint.

But how do you know if your existing constraints are column-level or table-level?

You can run any of the code examples below to determine whether your existing constraints are column-level or table-level. These retrieve all CHECK constraints for the current database, but you can always use a WHERE clause to narrow it down to a specific constraint.

Example 1 – Basic Query

Here’s a simple query that returns basic information about all CHECK constraints in the current database.

Here, I query the sys.check_constraints system view (which returns a row for each object that is a CHECK constraint, with sys.objects.type = 'C'). I only return four columns (but feel free to return as many columns as you like).

SELECT 
  Name,
  OBJECT_NAME(parent_object_id) AS 'Table',
  parent_column_id,
  Definition
FROM sys.check_constraints;

Result:

+-----------------+----------------+--------------------+----------------------------------------+
| Name            | Table          | parent_column_id   | Definition                             |
|-----------------+----------------+--------------------+----------------------------------------|
| chkPrice        | ConstraintTest | 2                  | ([Price]>(0))                          |
| chkValidEndDate | ConstraintTest | 0                  | ([EndDate]>=[StartDate])               |
| chkTeamSize     | ConstraintTest | 3                  | ([TeamSize]>=(5) AND [TeamSize]<=(20)) |
| chkJobTitle     | Occupation     | 3                  | ([JobTitle]<>'Digital Nomad')          |
+-----------------+----------------+--------------------+----------------------------------------+

The quickest way to determine which constraints are table-level constraints is to look for the zero (0) in the parent_column_id column. Anything with a zero is a table-level CHECK constraint. A non-zero value indicates that it’s a column-level CHECK constraint defined on the column with the specified ID value.

So in this example there are three column-level constraints and one table-level constraint.

Note that there are two constraints with the same parent_column_id (3), however, these two constraints are from different tables. The 3 refers to the third column of their respective tables.

As mentioned, if you only want info on a specific constraint, use a WHERE clause:

SELECT 
  Name,
  OBJECT_NAME(parent_object_id) AS 'Table',
  parent_column_id,
  Definition
FROM sys.check_constraints
WHERE name = 'chkPrice';

Result:

+----------+----------------+--------------------+---------------+
| Name     | Table          | parent_column_id   | Definition    |
|----------+----------------+--------------------+---------------|
| chkPrice | ConstraintTest | 2                  | ([Price]>(0)) |
+----------+----------------+--------------------+---------------+

Example 2 – Improve the Query

We can improve on the previous example by returning the parent column name instead of just its ID. Of course, this will return the column name for column-level constraints only. For table-level constraints NULL will be returned.

SELECT 
  cc.name AS 'Constraint',
  o.name AS 'Table',
  ac.name AS 'Column',
  cc.Definition AS 'Constraint Definition'
FROM sys.check_constraints cc
LEFT OUTER JOIN sys.objects o
  ON cc.parent_object_id = o.object_id
LEFT OUTER JOIN sys.all_columns ac
  ON cc.parent_column_id = ac.column_id
  AND cc.parent_object_id = ac.object_id;

Result:

+-----------------+----------------+----------+----------------------------------------+
| Constraint      | Table          | Column   | Constraint Definition                  |
|-----------------+----------------+----------+----------------------------------------|
| chkPrice        | ConstraintTest | Price    | ([Price]>(0))                          |
| chkValidEndDate | ConstraintTest | NULL     | ([EndDate]>=[StartDate])               |
| chkTeamSize     | ConstraintTest | TeamSize | ([TeamSize]>=(5) AND [TeamSize]<=(20)) |
| chkJobTitle     | Occupation     | JobTitle | ([JobTitle]<>'Digital Nomad')          |
+-----------------+----------------+----------+----------------------------------------+

Example 3 – Further Improvements

Let’s tweak the query some more:

SELECT 
  cc.name AS 'Constraint',
  cc.is_disabled AS 'Disabled?',
  CASE WHEN cc.parent_column_id = 0
    THEN 'Table-level'
    ELSE 'Column-level'
    END AS 'Table/Column',
  o.name AS 'Table',
  ISNULL(ac.name, '(n/a)') AS 'Column',
  cc.Definition AS 'Constraint Definition'
FROM sys.check_constraints cc
LEFT OUTER JOIN sys.objects o
  ON cc.parent_object_id = o.object_id
LEFT OUTER JOIN sys.all_columns ac
  ON cc.parent_column_id = ac.column_id
  AND cc.parent_object_id = ac.object_id;

Result:

+-----------------+-------------+----------------+----------------+----------+----------------------------------------+
| Constraint      | Disabled?   | Table/Column   | Table          | Column   | Constraint Definition                  |
|-----------------+-------------+----------------+----------------+----------+----------------------------------------|
| chkPrice        | 0           | Column-level   | ConstraintTest | Price    | ([Price]>(0))                          |
| chkValidEndDate | 0           | Table-level    | ConstraintTest | (n/a)    | ([EndDate]>=[StartDate])               |
| chkTeamSize     | 0           | Column-level   | ConstraintTest | TeamSize | ([TeamSize]>=(5) AND [TeamSize]<=(20)) |
| chkJobTitle     | 0           | Column-level   | Occupation     | JobTitle | ([JobTitle]<>'Digital Nomad')          |
+-----------------+-------------+----------------+----------------+----------+----------------------------------------+

So I now have the text “Column-level” or “Table-level” being returned, depending on which one it is.

I also use the ISNULL() function to turn any NULL values into “(n/a)”.

And I’ve also added the is_disabled column to the list, just in case any of the constraints have been disabled. You could always give this column the same treatment as the parent_column_id column and present “Yes” or “No” or “Enabled” or “Disabled” or similar.