为什么我的检查约束不会停止此空插入?

Dav*_*haw 13 sql-server sql-server-2008-r2

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

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

  • Code为null且System为null.
  • Code不是null并且System1.

我的第一个想法是ANSI NULLS,但设定它们onoff没有任何区别.

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

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
Run Code Online (Sandbox Code Playgroud)

Dam*_*ver 16

欢迎使用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 ????
Run Code Online (Sandbox Code Playgroud)

哪个(对于查询数据):

(FALSE AND TRUE)
OR
(TRUE AND UNKNOWN)
Run Code Online (Sandbox Code Playgroud)

任何UNKNOWN一方或另一方的运算符都评估为UNKNOWN,所以总体结果是UNKNOWN.哪个不是FALSE,因此评估检查约束是成功的.


如果你不想System为null,那么如果你把它作为一个额外的显式要求添加它,它对我来说是最清楚的.

([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 ????
Run Code Online (Sandbox Code Playgroud)

这似乎是一点点奇怪的是,这个被定义的方式,但它与其他约束的工作方式一致 - 一个外键约束可能具有空列如,而如果这些列是空的,也不必是引用表中的匹配行.

  • 对于当前情况,这无关紧要,但是此声明不正确:“并且一侧或另一侧都未知的任何运算符都将被评估为未知”。如果运算符为AND是正确的,但是如果运算符为OR并且您具有例如“ true OR unknown”,则它的计算结果为true,这也是非常合乎逻辑的,因为使用OR并不意味着您不知道一个值,只要其中之一为真。 (2认同)
  • 我什至必须在这里纠正自己,这种说法甚至在所有情况下都不正确。如果您具有“假AND未知”,则它不会求值为unknown,而是计算为false,这也是合乎逻辑的,因为如果使用AND运算符将一个值设为false,那么无论另一个值是什么,它将始终求值虚假。 (2认同)

Lie*_*ers 12

评估值的当前约束的结果123, NULL是Undefined.

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

结果是 Undefined

检查约束

CHECK约束拒绝评估为FALSE的值.因为null值计算为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 ????
)
Run Code Online (Sandbox Code Playgroud)

  • 在undefined之后我不会把(False)放在括号中.这绝对不是假的. (2认同)
  • 但你所添加的并不是真的.如果检查约束的最终结果是"UNKNOWN",那么它的处理方式就像它被评估为"TRUE"一样 - 这就是OP的惊讶. (2认同)