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

时间:2012-02-07 13:19:11

标签: sql-server sql-server-2008-r2

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

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

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

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

这是我们在应用程序中发现的更大问题的简化示例(系统是根据数字列表检查的 - IN(1, 2, etc.))。我们用一个外键(而不是IN)替换了这个检查,并且一个新的检查约束允许,无论是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

2 个答案:

答案 0 :(得分:15)

欢迎使用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 ????

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

答案 1 :(得分:12)

评估值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的值。因为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 ????
)