使用检查添加约束,然后检查约束与添加约束

问题描述 投票:0回答:9

我正在查看 SQL Server 2008 的 AdventureWorks 示例数据库,我在他们的创建脚本中看到他们倾向于使用以下内容:

ALTER TABLE [Production].[ProductCostHistory] WITH CHECK ADD 
CONSTRAINT [FK_ProductCostHistory_Product_ProductID] FOREIGN KEY([ProductID])
  REFERENCES [Production].[Product] ([ProductID])
GO

紧随其后的是:

ALTER TABLE [Production].[ProductCostHistory] CHECK CONSTRAINT     
[FK_ProductCostHistory_Product_ProductID]
GO

我看到外键(如此处)、唯一约束和常规

CHECK
约束;
DEFAULT
约束使用我更熟悉的常规格式,例如:

ALTER TABLE [Production].[ProductCostHistory] ADD  CONSTRAINT  
[DF_ProductCostHistory_ModifiedDate]  DEFAULT (getdate()) FOR [ModifiedDate]
GO

第一种方法与第二种方法之间有什么区别(如果有的话)?

sql sql-server t-sql check-constraints
9个回答
127
投票

第一个语法是多余的 -

WITH CHECK
是新约束的默认值,并且默认情况下该约束也是打开的。

此语法是由 SQL Management Studio 在生成 SQL 脚本时生成的 - 我假设它是某种额外的冗余,可能是为了确保即使表的默认约束行为发生更改也启用约束。


63
投票

演示这是如何工作的--

CREATE TABLE T1 (ID INT NOT NULL, SomeVal CHAR(1));
ALTER TABLE T1 ADD CONSTRAINT [PK_ID] PRIMARY KEY CLUSTERED (ID);

CREATE TABLE T2 (FKID INT, SomeOtherVal CHAR(2));

INSERT T1 (ID, SomeVal) SELECT 1, 'A';
INSERT T1 (ID, SomeVal) SELECT 2, 'B';

INSERT T2 (FKID, SomeOtherVal) SELECT 1, 'A1';
INSERT T2 (FKID, SomeOtherVal) SELECT 1, 'A2';
INSERT T2 (FKID, SomeOtherVal) SELECT 2, 'B1';
INSERT T2 (FKID, SomeOtherVal) SELECT 2, 'B2';
INSERT T2 (FKID, SomeOtherVal) SELECT 3, 'C1';  --orphan
INSERT T2 (FKID, SomeOtherVal) SELECT 3, 'C2';  --orphan

--Add the FK CONSTRAINT will fail because of existing orphaned records
ALTER TABLE T2 ADD CONSTRAINT FK_T2_T1 FOREIGN KEY (FKID) REFERENCES T1 (ID);   --fails

--Same as ADD above, but explicitly states the intent to CHECK the FK values before creating the CONSTRAINT
ALTER TABLE T2 WITH CHECK ADD CONSTRAINT FK_T2_T1 FOREIGN KEY (FKID) REFERENCES T1 (ID);    --fails

--Add the CONSTRAINT without checking existing values
ALTER TABLE T2 WITH NOCHECK ADD CONSTRAINT FK_T2_T1 FOREIGN KEY (FKID) REFERENCES T1 (ID);  --succeeds
ALTER TABLE T2 CHECK CONSTRAINT FK_T2_T1;   --succeeds since the CONSTRAINT is attributed as NOCHECK

--Attempt to enable CONSTRAINT fails due to orphans
ALTER TABLE T2 WITH CHECK CHECK CONSTRAINT FK_T2_T1;    --fails

--Remove orphans
DELETE FROM T2 WHERE FKID NOT IN (SELECT ID FROM T1);

--Enabling the CONSTRAINT succeeds
ALTER TABLE T2 WITH CHECK CHECK CONSTRAINT FK_T2_T1;    --succeeds; orphans removed

--Clean up
DROP TABLE T2;
DROP TABLE T1;

27
投票

除了上述关于可信约束的精彩评论:

select * from sys.foreign_keys where is_not_trusted = 1 ;
select * from sys.check_constraints where is_not_trusted = 1 ;

不受信任的约束,正如其名称所暗示的那样,不能被信任准确地表示表中数据当前的状态。但是,可以信任它来检查将来添加和修改的数据。

此外,查询优化器会忽略不受信任的约束。

启用检查约束和外键约束的代码相当糟糕,“检查”一词有三种含义。

ALTER TABLE [Production].[ProductCostHistory] 
WITH CHECK -- This means "Check the existing data in the table".
CHECK CONSTRAINT -- This means "enable the check or foreign key constraint".
[FK_ProductCostHistory_Product_ProductID] -- The name of the check or foreign key constraint, or "ALL".

17
投票
当表中的现有数据不符合定义的约束并且您不希望它与您正在实施的新约束发生冲突时,也可以使用

WITH NOCHECK
...


15
投票

WITH CHECK
确实是默认行为,但是将其包含在您的编码中是一个很好的做法。

替代行为当然是使用

WITH NOCHECK
,因此最好明确定义您的意图。当您使用/修改/切换内联分区时经常使用此功能。


11
投票

外键和检查约束具有可信或不可信以及启用和禁用的概念。有关完整详细信息,请参阅 MSDN 页面

ALTER TABLE

WITH CHECK
是添加新外键和检查约束的默认值,
WITH NOCHECK
是重新启用禁用的外键和检查约束的默认值。了解其中的差异很重要。

话虽如此,实用程序生成的任何明显冗余的语句只是为了安全和/或易于编码。别担心他们。


8
投票

这是我编写的一些代码,用于帮助我们识别和纠正数据库中不受信任的约束。它生成解决每个问题的代码。

    ;WITH Untrusted (ConstraintType, ConstraintName, ConstraintTable, ParentTable, IsDisabled, IsNotForReplication, IsNotTrusted, RowIndex) AS
(
    SELECT 
        'Untrusted FOREIGN KEY' AS FKType
        , fk.name AS FKName
        , OBJECT_NAME( fk.parent_object_id) AS FKTableName
        , OBJECT_NAME( fk.referenced_object_id) AS PKTableName 
        , fk.is_disabled
        , fk.is_not_for_replication
        , fk.is_not_trusted
        , ROW_NUMBER() OVER (ORDER BY OBJECT_NAME( fk.parent_object_id), OBJECT_NAME( fk.referenced_object_id), fk.name) AS RowIndex
    FROM 
        sys.foreign_keys fk 
    WHERE 
        is_ms_shipped = 0 
        AND fk.is_not_trusted = 1       

    UNION ALL

    SELECT 
        'Untrusted CHECK' AS KType
        , cc.name AS CKName
        , OBJECT_NAME( cc.parent_object_id) AS CKTableName
        , NULL AS ParentTable
        , cc.is_disabled
        , cc.is_not_for_replication
        , cc.is_not_trusted
        , ROW_NUMBER() OVER (ORDER BY OBJECT_NAME( cc.parent_object_id), cc.name) AS RowIndex
    FROM 
        sys.check_constraints cc 
    WHERE 
        cc.is_ms_shipped = 0
        AND cc.is_not_trusted = 1

)
SELECT 
    u.ConstraintType
    , u.ConstraintName
    , u.ConstraintTable
    , u.ParentTable
    , u.IsDisabled
    , u.IsNotForReplication
    , u.IsNotTrusted
    , u.RowIndex
    , 'RAISERROR( ''Now CHECKing {%i of %i)--> %s ON TABLE %s'', 0, 1' 
        + ', ' + CAST( u.RowIndex AS VARCHAR(64))
        + ', ' + CAST( x.CommandCount AS VARCHAR(64))
        + ', ' + '''' + QUOTENAME( u.ConstraintName) + '''' 
        + ', ' + '''' + QUOTENAME( u.ConstraintTable) + '''' 
        + ') WITH NOWAIT;'
    + 'ALTER TABLE ' + QUOTENAME( u.ConstraintTable) + ' WITH CHECK CHECK CONSTRAINT ' + QUOTENAME( u.ConstraintName) + ';' AS FIX_SQL
FROM Untrusted u
CROSS APPLY (SELECT COUNT(*) AS CommandCount FROM Untrusted WHERE ConstraintType = u.ConstraintType) x
ORDER BY ConstraintType, ConstraintTable, ParentTable;

0
投票

我敢说,感觉这可能是一个 SSMS(倒置逻辑)bug;因为第二个(现有/重新启用约束)语句需要显式包含/使用“WITH CHECK”,而不是第一个(默认为新/“with-check”)。

我想知道他们是否刚刚将“WITH CHECK”子句的生成应用于错误的 SQL 语句/第一个 T-SQL 语句而不是第二个 - 假设他们试图默认使用检查对于这两种情况 - 对于新约束或(重新启用)现有约束。

(对我来说似乎很有意义,因为禁用检查约束的时间越长,理论上同时出现损坏/检查约束无效数据的可能性就越大。)


0
投票

我已阅读此主题。谢谢!

因此,我还从 SSMS 运行了以下声明:

ALTER TABLE datadictionary.[TBFB-DataProductEntity] ADD CONSTRAINT
[FK_TBFB-DataProductEntity_TBFB-DataCategory] FOREIGN KEY
(
DataCategoryId
) REFERENCES datadictionary.[TBFB-DataCategory]
(
SystemId
) ON UPDATE  NO ACTION 
 ON DELETE  NO ACTION 

GO

但是,当从 SSMS 编写 datadictionary.TBFB-DataProductEntity 的 DDL 脚本时,它同时具有WITH CHECK ADD CONSTRAINT 和 CHECK CONSTRAINT,请参见下文:

/****** Object:  Table [datadictionary].[TBFB-DataProductEntity]    Script Date: 
4/22/2024 7:40:09 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [datadictionary].[TBFB-DataProductEntity](
[SystemId] [int] NOT NULL,
[DataCategoryId] [int] NULL,
PRIMARY KEY CLUSTERED 
(
[SystemId] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [datadictionary].[TBFB-DataProductEntity]  WITH CHECK ADD CONSTRAINT 
[FK_TBFB-DataProductEntity_TBFB-DataCategory] FOREIGN KEY([DataCategoryId])
REFERENCES [datadictionary].[TBFB-DataCategory] ([SystemId])
GO

ALTER TABLE [datadictionary].[TBFB-DataProductEntity] CHECK CONSTRAINT [FK_TBFB- 
DataProductEntity_TBFB-DataCategory]
GO

所以这是一个 SSMS 错误,因此我们应该忽略 CHECK CONSTRAINT,对吗?

© www.soinside.com 2019 - 2024. All rights reserved.