有人可以解释为什么SQL Server允许下面代码中的第三个插入(标记为查询数据)吗?
据我所知,检查约束应该只允许:
Code
为空,System
为空。Code
不为空且System
为1
。我的第一个想法是ANSI NULLS
,但设置它们on
或off
没有任何区别。
这是我们在应用程序中发现的更大问题的简化示例(系统是根据数字列表检查的 - 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
答案 0 :(得分:15)
欢迎使用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 ????
定义它的方式似乎有些奇怪,但它与其他约束的工作方式一致 - 例如外键约束可能具有可为空的列,如果其中任何列为空,则不必在引用的表中匹配行。
答案 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约束拒绝评估为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 ????
)