我正在查看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
第一种方式与第二种方式之间有什么区别?
答案 0 :(得分:86)
第一种语法是冗余的 - WITH CHECK是新约束的默认值,默认情况下也会启用约束。
这个语法是由SQL管理工作室在生成sql脚本时生成的 - 我假设它是某种额外的冗余,可能确保即使表的默认约束行为发生更改也会启用约束。
答案 1 :(得分:39)
演示如何运作 -
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;
答案 2 :(得分:20)
继上述关于可信约束的优秀评论之后:
select * from sys.foreign_keys where is_not_trusted = 1 ;
select * from sys.check_constraints where is_not_trusted = 1 ;
一个不受信任的约束,正如其名称所暗示的那样,不能被信任以准确表示现在表中数据的状态。但是,可以信任它来检查将来添加和修改的数据。
此外,查询优化器会忽略不受信任的约束。
启用检查约束和外键约束的代码非常糟糕,有三个含义“check”。
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".
答案 3 :(得分:15)
WITH NOCHECK
。 ..
答案 4 :(得分:13)
WITH CHECK
确实是默认行为,但在您的编码中包含这是一个好习惯。
替代行为当然是使用WITH NOCHECK
,因此明确定义您的意图是很好的。这通常在您使用/修改/切换内联分区时使用。
答案 5 :(得分:9)
外键和检查约束具有受信任或不受信任的概念,以及启用和禁用。有关详细信息,请参阅ALTER TABLE
的MSDN页面。
WITH CHECK
是添加新外键和检查约束的默认值,WITH NOCHECK
是重新启用禁用外键和检查约束的默认值。重要的是要意识到差异。
话虽如此,实用程序生成的任何明显冗余的语句都只是为了安全和/或编码的简易性。别担心。
答案 6 :(得分:7)
以下是我编写的一些代码,用于帮助我们识别和纠正数据库中不受信任的CONSTRAINT。它会生成修复每个问题的代码。
;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;
答案 7 :(得分:0)
我敢说,感觉这可能是一个 SSMS(倒置逻辑)错误;因为第二个(现有/重新启用约束)语句需要显式包含/使用“WITH CHECK”,而不是第一个(默认为新/“带检查”)。
我想知道他们是否刚刚将“WITH CHECK”子句的生成应用于错误的 SQL 语句/第一个 T-SQL 语句而不是第二个 - 假设他们试图默认使用检查两种情况 - 新约束或(重新启用)现有约束。
(对我来说似乎很有意义,因为检查约束被禁用的时间越长,理论上损坏/检查约束无效的数据可能在此期间潜入的可能性就越大。)