这篇文章打算全面介绍SQL检查约束的用法和其他细节。
什么是SQL检查约束
检查约束是帮助检查插入(或更新)到表中的数据值的规则或规则集,基于某个条件。这样,我们就可以在接受新插入的数据值到表之前,根据指定的规则对其进行验证。检查约束的主要优点是,它们确保一列中的所有数据都包含根据检查约束规则验证的值。
例如,我们为CountryList表定义了一个人口列,我们希望它只容纳正数数据。
CREATE TABLE CountryList (
Id INT IDENTITY PRIMARY KEY,
CountryName VARCHAR(255) NOT NULL,
CountryPopulation INT CHECK(CountryPopulation > 0)
)
我们可以看到CHECK(CountryPopulation > 0) 表达式参与了表的创建查询。这个表达式指定了一个SQL检查约束,并检查插入到CountryPopulation表中的数据是否大于0。如果插入的数据等于或小于0,记录就不能进入表,因为它将违反约束规则。
现在,在下面的插入查询中,我们将尝试向CountryPopulation列插入一个负值。
INSERT INTO CountryList
VALUES('PeaceCountry',-30)
我们可以看到,检查约束不允许插入未经指定规则验证的新数据,并返回一个错误。
指向多列的SQL检查约束
我们可以为引用多列的检查约束定义数据验证规则。在下面的例子中,我们要确保下一个人口普查日期必须大于上一个人口普查日期。为了创建这个数据验证规则,我们将在表创建查询中使用CHECK(LastCensus<NextCensus))表达式。
CREATE TABLE CountryListCensus (
Id INT IDENTITY PRIMARY KEY,
CountryName VARCHAR(255) NOT NULL,
CountryPopulation INT CHECK(CountryPopulation > 0),
LastCensus DATE,
NextCensus DATE,
CHECK(LastCensus<NextCensus)
)
现在我们想在CountryListCensus表中添加一条记录,但是LastCensus列和LastCensus列的值是相等的。在这种情况下,我们的查询将返回一个错误。
INSERT INTO CountryListCensus VALUES('PeaceCountry',10000,
'20210101','20210101')
SQL检查约束和NULL值
NULL值在SQL中表示一个未知的值,因此这个值被检查约束评估为UNKNOWN。由于这个原因,NULL值经常被检查约束评价为真。
INSERT INTO CountryList
VALUES( 'GreenCountry', NULL );
GO
SELECT *
FROM CountryList;
使用NOT NULL约束是控制一个字段是否包含NULL值的最好方法。
SQL检查约束和用户定义的函数
一个标量值的用户定义的函数在调用后会返回一个单一的值。我们可以在检查约束中使用这种类型的函数来定义一个数据验证规则。同时,我们可以将插入的数据值作为参数传递给这个函数。例如,下面的函数计算了2个日期之间的年差,我们将在检查约束中使用这个函数。
CREATE FUNCTION CalcTwoDateDiff
(@EndDate DATE,@BegDate DATE)
RETURNS INT
AS
BEGIN
DECLARE @Result AS INT=0
SELECT @Result=DATEDIFF(YEAR,@BegDate,@EndDate)
RETURN @Result
END
这时,我们在现有的表中添加一个新的检查约束。为了给现有的表添加一个新的约束,我们需要给出一个约束的名称。
ALTER TABLE CountryListCensus
ADD CONSTRAINT CheckDateDiff
CHECK (dbo.CalcTwoDateDiff(NextCensus,LastCensus)=4)
我们可以看到,检查约束规则控制了两次普查之间必须有4年的时间。下面的插入语句将被成功执行。
INSERT INTO CountryListCensus VALUES('PeaceCountry',10000,
'20170101','20210101')
禁用一个检查约束
在某些情况下,我们可能需要在SQL检查约束规则中做一个例外,插入一些不验证检查约束的数据。为了解决这个问题,我们可以重新考虑检查约束规则或暂时禁用检查约束。要禁用任何检查约束,我们必须首先找到它的名称和定义在哪个表中。下面的查询列出了CountryListCensus的定义约束。
SELECT
name,
is_disabled,
is_not_trusted,
definition,
OBJECT_NAME(parent_object_id) AS TableName
FROM sys.check_constraints
WHERE OBJECT_NAME(parent_object_id)= 'CountryList'
作为第二步,我们通过下面的查询禁用与国家人口有关的约束。
ALTER TABLE CountryList
NOCHECK CONSTRAINT CK__CountryLi__Count__62AFA012;
之后,我们可以在CountryList列中插入负值。
INSERT INTO CountryList VALUES('PeaceCountry',-30)
GO
SELECT * FROM CountryList
在禁用约束后,再将数据插入到表中,我们可以借助下面的查询来重新启用约束。
ALTER TABLE CountryList
CHECK CONSTRAINT CK__CountryLi__Count__62AFA012;
当我们重新检查约束的状态时,状态将显示为enable,但信任状态将显示为disable。
SELECT
name,
is_disabled,
is_not_trusted,
definition,
OBJECT_NAME(parent_object_id) AS TableName
FROM sys.check_constraints
WHERE OBJECT_NAME(parent_object_id)= 'CountryList'
这意味着系统没有对所有的行进行检查约束的验证。SQL Server使用检查约束来提高查询的性能。然后,数据引擎将不得不从表中读取数据。现在我们来看看检查约束是如何帮助提高查询性能的。在Adventureworks数据库中,我们将执行以下查询并分析执行计划。
SET STATISTICS IO ON
SELECT FirstName,LastName FROM Person.Person WHERE EmailPromotion = 4
正如我们所看到的,查询优化器返回一个非常简单的查询计划,因为它知道由于CK_Person_EMailPromotion约束,EmailPromotion列必须只包括0到2之间的值。
同时,这个查询没有进行任何物理或逻辑上的读取。现在我们将禁用CK_Person_EmailPromotion检查约束,然后重新执行相同的查询。
ALTER TABLE Person.Person
NOCHECK CONSTRAINT CK_Person_EmailPromotion;
在执行同样条件的查询后,我们会发现执行计划完全不同。
SET STATISTICS IO ON
SELECT FirstName,LastName FROM Person.Person WHERE
EmailPromotion = 4
除了这个执行计划的改变,查询已经进行了3821次逻辑读取。
现在,我们将重新启用CK_Person_EmailPromotion约束并重新执行我们的示例查询。
ALTER TABLE Person.Person
CHECK CONSTRAINT CK_Person_EmailPromotion;
重新启用约束后,查询的执行计划不会改变,因为约束仍然处于不被信任的状态。
SET STATISTICS IO ON
SELECT FirstName,LastName FROM Person.Person WHERE
EmailPromotion = 4
结语
在这篇文章中,我们已经探讨了SQL检查约束的使用细节。同时,检查约束可以帮助提高查询的性能。