为啥我的检查约束没有停止这个空插入?

Posted

技术标签:

【中文标题】为啥我的检查约束没有停止这个空插入?【英文标题】:Why is my check constraint not stopping this null insert?为什么我的检查约束没有停止这个空插入? 【发布时间】:2012-02-07 13:19:11 【问题描述】:

谁能解释一下为什么 SQL Server 允许下面代码中的第三个插入(标记为 Query Data)?

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

Code 为空,System 为空。 Code 不为空,System1

我的第一个想法是ANSI NULLS,但将它们设置为onoff 没有区别。

这是我们在应用程序中发现的一个更大问题的简化示例(系统已根据数字列表进行检查 - IN(1, 2, etc.))。我们用一个外键(而不是IN)和一个新的检查约束替换了这个检查,它允许两者都为空或两者都不为空;这样做会阻止第三次插入。

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

【问题讨论】:

【参考方案1】:

欢迎来到 SQL 精彩的三值逻辑。您可能知道也可能不知道,与null 进行任何标准比较的结果不是TRUEFALSE,而是UNKNOWN

WHERE 子句中,整个子句必须评估为TRUE

CHECK 约束中,整个约束必须评估为不是FALSE

所以,我们有:

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

变成(查询数据):

(FALSE AND TRUE)
OR
(TRUE AND UNKNOWN)

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


如果您希望 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 - 我认为你需要设置它OFF 可能有其他副作用 @DaveShaw - 这是 ANSI 行为,你已经设置了 ANSI_NULLS ON (正如你应该的那样 - 使用带有 ANSI_NULLS OFF 的 SQL Server 确实不是一个好主意,因为有一天它会 @987654321 @) 对于当前的情况,这无关紧要,但这种说法是不正确的“并且任何一侧或另一侧具有 UNKNOWN 的运算符评估为 UNKNOWN”。如果运算符是 AND 是正确的,但如果运算符是 OR 并且您有例如“true OR unknown”,那么它的计算结果为 true,这也是非常合乎逻辑的,因为使用 OR,如果您不知道,则无关紧要的值,只要其中一个为真。 我什至必须在这里纠正自己,在所有情况下,对于 AND 的声明甚至都不正确。如果您有“假与未知”,它不会评估为未知,它会评估为假,这也是合乎逻辑的,因为如果使用 AND 运算符的一个值是假的,那么另一个值是什么都没有关系,它总是会评估为假。【参考方案2】:

评估值123, NULL 的当前约束的结果是未定义。

([Code] IS NULL AND [System] IS NULL) 评估为 False ([Code] IS NOT NULL AND [System] IN (1, 2, 3)) 评估为 Undefined

结果是Undefined

Check Constraint

CHECK 约束拒绝评估为 FALSE 的值。因为空 值评估为 UNKNOWN,它们在表达式中的存在可能会覆盖 一个约束。

您应该将支票上的 [System] IN (1, 2, 3) 更改为 ISNULL([System], 0) IN (1, 2, 3)

你的检查约束就变成了

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

【讨论】:

我不会在未定义之后将 (False) 放在括号中。这绝对不是假的。 @Damien_The_Unbeliever - 我知道你的意思,但我已经将它添加到括号中,说明undefined 对最终结果的作用。我会将此评论添加到答案中。 但是你添加的不是真的。如果检查约束的最终结果是UNKNOWN,那么它的处理方式与评估为TRUE 相同——这让OP 感到惊讶。 @Damien_The_Unbeliever - 你说对了forehead> @Damien_The_Unbeliever - 我已经改变了我的答案,并赞成你第一次正确和更好地解释它。

以上是关于为啥我的检查约束没有停止这个空插入?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 Check 约束不起作用?

主键约束,外键约束,空值约束,默认值约束,唯一约束,检查约束的各个作用是啥?

非空约束对数据插入的影响

oracle--约束(主键非空检查)

为啥在MySQL数据库中建立检查约束不成功呢,语句是这样的

SQLite 插入约束字段时出错 - 为啥?