What SQL databases support subqueries in CHECK constraints?
Asked Answered
A

6

9

What SQL databases, if any, support subqueries in CHECK constraints?

At present and as far as I know, Oracle, MySQL, and PostgreSQL do not.

EDIT

(Clarification based on initial answers.) I'm looking for something like this:

CREATE TABLE personnel (
  ...,
department VARCHAR(64) NOT NULL,
salary NUMERIC NOT NULL,
CHECK (salary >= (SELECT MIN(p.salary) FROM payranges p WHERE p.dept = department)
        AND
       salary <= (SELECT MAX(p.salary) FROM payranges p WHERE p.dept = department)
)

UPDATE

MS Access and Firebird both support this feature.

Aiglet answered 1/6, 2011 at 3:14 Comment(0)
T
7

The Access database engine (ACE, Jet, whatever) supports subqueries in CHECK constraints but I hesitate to call it a SQL DBMS because it doesn't support entry level Standard SQL-92 and Access CHECK constraints are barely documented by MS and the Access Team.

For example, I can demonstrate that Access CHECK constraints are checked for each row affected (SQL-92 specifies that they should be checked after each SQL statement) but whether this is a bug or a feature we do not know because there is no documentation to refer to.


Here's a very simple example of a CHECK constraint that comprises a subquery. It is compliant with Full SQL-92 and works well in Access. The idea is to restrict the table to a maximum of two rows (the following SQL DDL requires ANSI-92 Query Mode e.g. use an ADO connection such as Access.CurrentProject.Connection):

CREATE TABLE T1 
(
 c INTEGER NOT NULL UNIQUE
);

ALTER TABLE T1 ADD
   CONSTRAINT max_two_rows
      CHECK (
             NOT EXISTS (
                         SELECT 1
                           FROM T1 AS T
                         HAVING COUNT(*) > 2
                        )
            );

However, here is a further example that is SQL-92, can be created in Access (some valid CHECKs fail in Access with a horrid crash that requires my machine to be restarted :( but doesn't function properly. The idea is to only allow exactly two rows in the table (or zero rows: constraints are not tested for an empty table):

CREATE TABLE T2 
( 
 c INTEGER NOT NULL UNIQUE 
);

ALTER TABLE T2 ADD 
   CONSTRAINT exactly_two_rows 
      CHECK ( 
             NOT EXISTS ( 
                         SELECT 1 
                           FROM T2 AS T 
                         HAVING COUNT(*) <> 2 
                        ) 
            );

Attempt to INSERT two rows in the same statement e.g. (assuming table T1 has at least one row):

SELECT DT1.c
  FROM (
        SELECT DISTINCT 1 AS c
          FROM T1
        UNION ALL
        SELECT DISTINCT 2
          FROM T1
       ) AS DT1;

However, this causes the CHECK to bite. This (and further testing) implies that the CHECK is tested after each row is added to the table, whereas SQL-92 specifies that constraints are tested at the SQL statement level.

It shouldn't come as too much of a surprise that Access has truly table-level CHECK constraints when you consider that until Access2010 it didn't have any trigger functionality and certain oft-used tables would otherwise have no true key (e.g. the 'sequenced' key in a valid-state temporal table). Note that Access2010 triggers suffer the same bug/feature that they are tested at the row level, rather than at the statement level.

The following is VBA to reproduce the two scenarios described above. Copy and paste into any VBA/VB6 standard .bas module (e.g. use Excel), no references required. Creates a new .mdb in your temp folder, creates the tables, data and tests that the constraints work/do not work (hint: set a breakpoint, step through the code, reading the comments):

Sub AccessCheckSubqueryButProblem()

  On Error Resume Next
  Kill Environ$("temp") & "\DropMe.mdb"
  On Error GoTo 0

  Dim cat
  Set cat = CreateObject("ADOX.Catalog")
  With cat
    .Create _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"
    With .ActiveConnection

      Dim Sql As String

      Sql = _
      "CREATE TABLE T1 " & vbCr & _
      "( " & vbCr & _
      " c INTEGER NOT NULL UNIQUE " & vbCr & _
      ");"
      .Execute Sql

      Sql = _
      "ALTER TABLE T1 ADD " & vbCr & _
      "   CONSTRAINT max_two_rows " & vbCr & _
      "      CHECK ( " & vbCr & _
      "             NOT EXISTS ( " & vbCr & _
      "                         SELECT 1 " & vbCr & _
      "                           FROM T1 AS T " & vbCr & _
      "                         HAVING COUNT(*) > 2 " & vbCr & _
      "                        ) " & vbCr & _
      "            );"
      .Execute Sql

      Sql = _
      "INSERT INTO T1 (c) VALUES (1);"
      .Execute Sql

      Sql = _
      "INSERT INTO T1 (c) VALUES (2);"
      .Execute Sql

      ' The third row should (and does)
      ' cause the CHECK to bite
      On Error Resume Next
      Sql = _
      "INSERT INTO T1 (c) VALUES (3);"
      .Execute Sql
      MsgBox Err.Description
      On Error GoTo 0

      Sql = _
      "CREATE TABLE T2 " & vbCr & _
      "( " & vbCr & _
      " c INTEGER NOT NULL UNIQUE " & vbCr & _
      ");"
      .Execute Sql

      Sql = _
      "ALTER TABLE T2 ADD " & vbCr & _
      "   CONSTRAINT exactly_two_rows " & vbCr & _
      "      CHECK ( " & vbCr & _
      "             NOT EXISTS ( " & vbCr & _
      "                         SELECT 1 " & vbCr & _
      "                           FROM T2 AS T " & vbCr & _
      "                         HAVING COUNT(*) <> 2 " & vbCr & _
      "                        ) " & vbCr & _
      "            );"
      .Execute Sql

      ' INSERTing two rows in the same SQL statement
      ' should succeed according to SQL-92
      ' but fails (and we have no docs from MS
      ' to indicate whether this is a bug/feature)
      On Error Resume Next
      Sql = _
      "INSERT INTO T2 " & vbCr & _
      "   SELECT c " & vbCr & _
      "     FROM T1;"
      .Execute Sql
      MsgBox Err.Description
      On Error GoTo 0


    End With
    Set .ActiveConnection = Nothing
  End With
End Sub
Trichromat answered 1/6, 2011 at 5:42 Comment(4)
+1 MS Access? I scarcely believe it. :) Can you show me an example of a working Access CHECK constraint with a subquery in it?Aiglet
@pilcrow: well, you could go upvote some of my answers to other questions... :) Only kidding, I have enough votes already, and thanks for the kind words.Trichromat
Thanks for the example with `not exists'. That did the trick for me.Businesslike
+1 do you know if the CHECK is performed before INSERT and UPDATE on Firebird?Onassis
I
4

Firebird documentation says it allows subqueries in CHECK constraints.

Inexistent answered 29/3, 2012 at 0:16 Comment(5)
@Gracchus Can you provide a more detailed explanation of your requirements? (sample data or code would be useful)Endsley
thank you very much! is it practical to replace foreign keys with subqueried checks? i have a table with 2 types of data that are more or less equal except for a boolean flag. to reference that table's primary conditional upon the flag, should i just abandon the foreign key without a conditional and use subqueried checks instead? (sorry about deleting my previous comment, but this just hit me and possibly rendered my question post moot)Onassis
@Gracchus I wouldn't use checks with subqueries. I'd prefer to split/normalize the tables so a proper foreign key constraint is defined. If you write a question with your example, leave a comment here and I'll check. Or even better, write the question at the sister site: dba.seEndsley
thank you so very much! why would you go that way? (doing my best to avoid chat) would you mind posting a link that explains your reasoning? thanks again in advance!Onassis
@Gracchus Read the answer by onedaywhen in this question! Sorry, I had this answer in mind: Why don't DBMS's support ASSERTIONEndsley
B
2

SQL Server 2000+ allows UDFs that contain queries: you can't use sub-queries directly

However, they are not concurrent under high loads

Biscuit answered 1/6, 2011 at 4:43 Comment(2)
Also, CHECK constraints are supposed to be checked after each SQL statement (or transaction end if deferred but SQL doesn't support this) but SQL Server checks them for each row affected. See Trouble with CHECK ConstraintsTrichromat
Nice linked article, @onedaywhen.Aiglet
H
1

H2 also supports subqueries in constraints. In Psql mode no less :P

MariaDB does not seems to also support it as a constraint.

ALTER TABLE Table_1 ADD CONSTRAINT constraint_1 
CHECK (column_1 > (SELECT MAX(column_2) FROM Table_2) NOT DEFERRABLE;

Important Because the book is about the SQL-99 standard, the contents of this and other pages in the book may not directly apply to MariaDB. Use the navigation bar to navigate the book.

This sort of thing was once illegal, but in modern variations of SQL, you'll see inter-table Constraint references on an occasional basis.

For reference, this is the ticket for implementing check constraints on MariaDB. As of 2015-07-23, it is still in "Open" status.

Humbertohumble answered 22/7, 2015 at 7:1 Comment(2)
What versions introduced this support? (It's been a while since this question was posed.)Aiglet
Sorry, I made a mistake, apparently the link I posted for MariaDB is not about MariaDB itself, but rather a mirror of a book on SQL99, which is hosted on the MariaDB website. I will edit my answer to reflect this new finding. However, H2 does indeed support it (verified locally), but I don't know since what version.Humbertohumble
R
0

Pretty sure TRIGGER will work in each of the databases you mentioned and you get a lot more "elbow room" to work out your constraint.

Rather answered 1/6, 2011 at 3:30 Comment(3)
Absolutely triggers will work -- but that's not the question. :)Aiglet
Triggers are not re-run when the table mentioned in the subquery changes. Therefore triggers do only work if triggers are set up for multiple tables or the tables mentioned in the subquery is guaranteed not to change.Delaine
"you get a lot more "elbow room" [with TRIGGER]" -- but you lose a lot of functionality that you get 'for free' with declarable constraints and have to manage them yourself in a trigger: concurrency/serialization, optimization, etc not to mention coding the data integrity query correctly in the first place (multi-tuple constraints tend to be non-trival compared to attribute constraints). I tend to think, "More (elbow) room to get things wrong"!Trichromat
M
-3

SQL server supports it You can find valuable information in the following link

http://www.craigsmullins.com/sql_1298.htm

They say POSTGRESQL also supports it

http://developer.postgresql.org/pgdocs/postgres/ddl-constraints.html

DB2 supports CHECK constraint

http://www.ibm.com/developerworks/data/library/techarticle/dm-0401melnyk/

Matherne answered 1/6, 2011 at 3:19 Comment(2)
I'm pretty sure that SQL Server does not allow subqueries in check constraints. The msdn entries all talk about only referring to the column in question (in a column-level constraint) or to columns in the table (in a table level constraint). Here's also a forum post where the submitter said that it didn't work, and they were encouraged to use triggers instead .Vezza
No, these links show only ordinary CHECK constraints, not subqueries within CHECK constraints.Aiglet

© 2022 - 2024 — McMap. All rights reserved.