Sql server 为什么我的检查约束不能停止这个空插入?

Sql server 为什么我的检查约束不能停止这个空插入?,sql-server,sql-server-2008-r2,Sql Server,Sql Server 2008 R2,有人能解释为什么SQL Server允许下面代码中的第三个插入(标记为查询数据)吗 据我所知,检查约束只允许: code为空,System为空 code不为空,System为1 我的第一个想法是ANSI NULLS,但将它们设置为打开或关闭没有任何区别 这是我们在应用程序中发现的一个更大问题的简化示例(系统根据(1、2等)中的数字列表进行了检查)。我们用一个外键(而不是中的)和一个新的检查约束替换了该检查,该约束允许null或notnull;这样做防止了第三次插入 IF EXISTS (S

有人能解释为什么SQL Server允许下面代码中的第三个插入(标记为查询数据)吗

据我所知,检查约束只允许:

  • code
    为空,
    System
    为空
  • code
    不为空,
    System
    1
我的第一个想法是
ANSI NULLS
,但将它们设置为
打开
关闭
没有任何区别

这是我们在应用程序中发现的一个更大问题的简化示例(系统根据(1、2等)中的数字列表进行了检查)。我们用一个外键(而不是中的
)和一个新的检查约束替换了该检查,该约束允许null或notnull;这样做防止了第三次插入

IF  EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[CK_TestCheck]') AND parent_object_id = OBJECT_ID(N'[dbo].[TestCheck]'))
    ALTER TABLE [dbo].[TestCheck] DROP CONSTRAINT [CK_TestCheck]
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestCheck]') AND type in (N'U'))
    DROP TABLE [dbo].[TestCheck]
GO

SET ANSI_NULLS ON
GO

CREATE TABLE TestCheck(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Code] [varchar](50) NULL,
    [System] [tinyint] NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC))
GO

ALTER TABLE [dbo].[TestCheck] WITH CHECK ADD CONSTRAINT [CK_TestCheck] CHECK
(
    ([Code] IS NULL AND [System] IS NULL)   --Both null
    OR
    ([Code] IS NOT NULL AND [System] = 1)   --Both not null ????
)
GO

ALTER TABLE [dbo].[TestCheck] CHECK CONSTRAINT [CK_TestCheck]
GO

--Good Data
insert TestCheck (Code, [System]) Values(null, null);
insert TestCheck (Code, [System]) Values('123', 1);

--Query Data
insert TestCheck (Code, [System]) Values('123', null);

--Bad data stopped
insert TestCheck (Code, [System]) Values(null, 1);
insert TestCheck (Code, [System]) Values('123', 4);

select * from TestCheck
Where
    case when
    (
        ([Code] IS NULL AND [System] IS NULL)           --Both null
        OR
        ([Code] IS NOT NULL AND [System] in (1, 2, 3))  --Both not null ????
    )
    then 0 else 1 end
     = 1

对值
123,NULL
的当前约束求值的结果未定义

  • ([code]为空,[System]为空)
    计算结果为
    False
  • ([code]不为空,并且(1,2,3)中的[System]计算结果为
    未定义
结果是
未定义

检查约束拒绝计算为FALSE的值。因为空 值计算为未知值,表达式中的值可能会覆盖 约束

您应该将(1,2,3)
中对
[System]的检查更改为(1,2,3)
中的
ISNULL([System],0)

您的检查约束将变为

ALTER TABLE [dbo].[TestCheck] WITH CHECK ADD CONSTRAINT [CK_TestCheck] CHECK
(
    ([Code] IS NULL AND [System] IS NULL)   --Both null
    OR
    ([Code] IS NOT NULL AND ISNULL([System], 0) IN (1, 2, 3))   --Both not null ????
)

欢迎使用SQL奇妙的三值逻辑。您可能知道,也可能不知道,与
null
进行任何标准比较的结果都不是
TRUE
,或
FALSE
,而是
未知

WHERE
子句中,整个子句的计算结果必须为
TRUE

检查
约束中,整个约束的计算结果必须不是
FALSE

因此,我们有:

([Code] IS NULL AND [System] IS NULL)   --Both null
OR
([Code] IS NOT NULL AND [System] = 1)   --Both not null ????
它变成(对于查询数据):

而任何一侧带有
UNKNOWN
的运算符或另一侧的运算符计算结果为
UNKNOWN
,因此总体结果为
UNKNOWN
。这不是FALSE,因此评估check约束是成功的


如果您希望
System
不为空,那么我最清楚的是,如果您将其添加为一个额外的明确要求

([Code] IS NULL AND [System] IS NULL)   --Both null
OR
([Code] IS NOT NULL AND [System] IS NOT NULL AND [System] = 1)   --Both not null ????


它的定义方式可能有点奇怪,但它与其他约束的工作方式是一致的,例如,外键约束可能有可为空的列,如果这些列中有任何一列为空,则引用表中不必有匹配的行。

谢谢,我的印象是,设置ANSI_NULL是控制这种行为的一种方法?@DaveShaw-我认为您需要将其设置为
关闭
,这可能有另一面effects@DaveShaw-这是ANSI行为,您已在
(正如您应该做的那样-使用SQL Server并关闭
ANSI_NULLS
确实不是一个好主意,因为总有一天会这样做)对于当前的情况来说,这并不重要,但该声明是不正确的,“并且任何一边未知或另一边未知的运算符都会计算为未知”。如果运算符为和,则为正确,但如果运算符为或,则您有例如“true或unknown”然后它的计算结果为真,这也是非常合乎逻辑的,因为使用OR,如果您不知道其中一个值,只要其中一个值为真,这并不重要。我甚至必须在这里更正我自己,该声明在所有情况下都不正确。如果您有“false AND unknown”它的计算结果不是未知的,它的计算结果是false,这也是合乎逻辑的,因为如果一个值使用AND运算符为false,那么不管另一个值是什么,它都将计算为false在未定义后的括号中。这绝对不是错的。@Damien_不信者-我知道你的意思,但我已将它添加到括号中,说明
未定义
对最终结果的作用。我将在答案中添加此注释。但你添加的内容不是真的。如果检查约束的最终结果是
未知
,则不是创建的结果与评估为
真的结果一样
-这让OP感到惊讶。@Damien_the_unsiver-你是对的offcourse@Damien_The_Unbeliever-我已经改变了我的答案,并对你的答案投了赞成票,因为你第一次解释的正确和更好。
([Code] IS NULL AND [System] IS NULL)   --Both null
OR
([Code] IS NOT NULL AND [System] IS NOT NULL AND [System] = 1)   --Both not null ????