【问题标题】:What SQL databases support subqueries in CHECK constraints?哪些 SQL 数据库支持 CHECK 约束中的子查询?
【发布时间】:2011-06-01 03:14:28
【问题描述】:

哪些 SQL 数据库(如果有)支持 CHECK 约束中的子查询?

目前,据我所知,Oracle、MySQL 和 PostgreSQL 还没有。

编辑

(基于初步答案的澄清。)我正在寻找这样的东西:

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)
)

更新

MS Access 和 Firebird 都支持此功能。

【问题讨论】:

    标签: sql subquery check-constraints


    【解决方案1】:

    Access 数据库引擎(ACE、Jet 等)支持 CHECK 约束中的子查询,但我不愿称其为 SQL DBMS,因为它不支持入门级标准 SQL-92 和MS 和 Access 团队几乎没有记录 Access CHECK 约束。

    例如,我可以演示 Access CHECK 约束检查受影响的每一行(SQL-92 指定应该在每个 SQL 语句之后检查它们)但这是一个错误还是一个我们不知道的功能,因为没有可参考的文档。


    这是一个包含子查询的 CHECK 约束的非常简单的示例。它符合完整的 SQL-92 并且在 Access 中运行良好。想法是将表限制为最多两行(以下 SQL DDL 需要 ANSI-92 Query Mode 例如使用 ADO 连接,例如 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
                            )
                );
    

    但是,这里还有一个 SQL-92 示例,可以在 Access 中创建(一些有效的CHECKs 在 Access 中失败,出现可怕的崩溃,需要重新启动我的机器 :( 但不能正常运行. 这个想法是只允许表中恰好有两行(或零行:不对空表测试约束):

    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 
                            ) 
                );
    

    尝试在同一语句中插入两行,例如(假设表T1 至少有一行):

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

    但是,这会导致CHECK 咬人。这(以及进一步的测试)意味着CHECK 在每行添加到表后进行测试,而 SQL-92 指定在 SQL 语句级别测试约束。

    当您考虑到在 Access2010 之前它没有任何触发器功能并且某些经常使用的表在其他情况下没有真正的情况时,Access 具有真正的表级 CHECK 约束,这应该不足为奇键(例如,有效状态时态表中的“排序”键)。请注意,Access2010 触发器与它们在行级别而不是语句级别进行测试的错误/功能相同。

    以下是复现上述两种场景的VBA。复制并粘贴到任何 VBA/VB6 标准 .bas 模块(例如使用 Excel)中,无需参考。在您的临时文件夹中创建一个新的 .mdb,创建约束工作/不工作的表、数据和测试(提示:设置断点,单步执行代码,读取 cmets):

    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
    

    【讨论】:

    • +1 MS 访问权限?我几乎不相信。 :) 你能告诉我一个带有子查询的有效 Access CHECK 约束的例子吗?
    • @pilcrow:好吧,你可以投票赞成我对其他问题的一些回答... :) 开个玩笑,我已经有足够的选票了,谢谢你的好话。
    • 感谢“不存在”的示例。这对我有用。
    • +1 你知道在 Firebird 上 CHECK 是否在 INSERTUPDATE 之前执行吗?
    【解决方案2】:

    Firebird documentation 表示它允许在 CHECK 约束中进行子查询。

    【讨论】:

    • @Gracchus 您能否更详细地说明您的要求? (示例数据或代码会很有用)
    • 非常感谢!用子查询检查替换外键是否可行?我有一个表,其中包含 2 种类型的数据,它们或多或少相等,但布尔标志除外。要根据标志引用该表的主要条件,我是否应该放弃没有条件的外键并改用子查询检查? (很抱歉删除我之前的评论,但这只是打击了我,并可能使我的问题变得毫无意义)
    • @Gracchus 我不会对子查询使用检查。我更喜欢拆分/规范化表,以便定义适当的外键约束。如果你用你的例子写了一个问题,请在这里发表评论,我会检查的。或者更好的是,在姐妹网站上写下问题:dba.se
    • 非常感谢!你为什么要那样做? (尽我所能避免聊天)你介意发布一个链接来解释你的推理吗?再次提前感谢!
    • @Gracchus 在这个问题中阅读答案!抱歉,我想到了这个答案:Why don't DBMS's support ASSERTION
    【解决方案3】:

    SQL Server 2000+ 允许包含查询的 UDF:您不能直接使用子查询

    但是,他们不是concurrent under high loads

    【讨论】:

    • 另外,CHECK 约束应该在每个 SQL 语句之后检查(或者事务结束,如果延迟但 SQL 不支持),但 SQL Server 会检查受影响的每一行。见Trouble with CHECK Constraints
    • 不错的链接文章,@onedaywhen。
    【解决方案4】:

    H2 还支持约束中的子查询。在 Psql 模式下也不少 :P

    MariaDB 似乎也支持它as a constraint

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

    重要 因为本书是关于 SQL-99 标准的,所以本书中这一页和其他页面的内容可能并不直接适用于 MariaDB。使用导航栏浏览图书。

    这种事情曾经是非法的,但在 SQL 的现代变体中, 您会偶尔看到表间约束引用。

    供参考,this is the ticket for implementing check constraints on MariaDB。截至 2015 年 7 月 23 日,它仍处于“开放”状态。

    【讨论】:

    • 哪些版本引入了这种支持? (这个问题提出已经有一段时间了。)
    • 对不起,我犯了一个错误,显然我为 MariaDB 发布的链接不是关于 MariaDB 本身,而是一本关于 SQL99 的书的镜像,该书托管在 MariaDB 网站上。我将编辑我的答案以反映这一新发现。但是,H2确实确实支持(本地验证),但不知道是从什么版本开始的。
    【解决方案5】:

    很确定 TRIGGER 将在您提到的每个数据库中工作,并且您有更多的“肘部空间”来解决您的约束。

    【讨论】:

    • 绝对触发器会起作用——但这不是问题所在。 :)
    • 当子查询中提到的表发生变化时,触发器不会重新运行。因此,触发器仅在为多个表设置触发器或保证子查询中提到的表不会更改时才起作用。
    • “你得到了更多的“肘部空间”[with TRIGGER]”——但是你失去了很多你可以“免费”获得的功能,并且必须自己管理它们触发器:并发/序列化、优化等,更不用说首先正确编码数据完整性查询(与属性约束相比,多元组约束往往不平凡)。我倾向于认为,“更多(肘部)空间会出错”!
    【解决方案6】:

    SQL 服务器支持 您可以在以下链接中找到有价值的信息

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

    他们说 POSTGRESQL 也支持它

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

    DB2 支持 CHECK 约束

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

    【讨论】:

    • 我很确定 SQL Server 不允许在检查约束中使用子查询。 msdn 条目都只讨论有问题的列(在列级约束中)或表中的列(在表级约束中)。 Here 也是一个论坛帖子,提交者说它不起作用,并鼓励他们改用触发器。
    • 不,这些链接只显示普通的 CHECK 约束,而不是 CHECK 约束中的子查询
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    • 1970-01-01
    • 1970-01-01
    • 2021-01-27
    • 1970-01-01
    • 2018-02-16
    • 2013-07-03
    相关资源
    最近更新 更多