为啥我的检查约束没有停止这个空插入?
Posted
技术标签:
【中文标题】为啥我的检查约束没有停止这个空插入?【英文标题】:Why is my check constraint not stopping this null insert?为什么我的检查约束没有停止这个空插入? 【发布时间】:2012-02-07 13:19:11 【问题描述】:谁能解释一下为什么 SQL Server 允许下面代码中的第三个插入(标记为 Query Data)?
据我所知,检查约束应该只允许:
Code
为空,System
为空。
Code
不为空,System
为 1
。
我的第一个想法是ANSI NULLS
,但将它们设置为on
或off
没有区别。
这是我们在应用程序中发现的一个更大问题的简化示例(系统已根据数字列表进行检查 - 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
进行任何标准比较的结果不是TRUE
或FALSE
,而是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 - 以上是关于为啥我的检查约束没有停止这个空插入?的主要内容,如果未能解决你的问题,请参考以下文章