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
1つ目の方法と2つ目の方法の違いはありますか?
最初の構文は冗長です-WITH CHECKは新しい制約のデフォルトであり、制約もデフォルトでオンになります。
この構文は、SQLスクリプトの生成時にSQL管理スタジオによって生成されます-テーブルのデフォルトの制約動作が変更された場合でも制約が有効になるようにするために、何らかの余分な冗長性があると想定しています。
これがどのように機能するかを示すために-
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;
上記の信頼できる制約に関する優れたコメントに加えて:
select * from sys.foreign_keys where is_not_trusted = 1 ;
select * from sys.check_constraints where is_not_trusted = 1 ;
信頼できない制約は、その名前が示すように、現時点ではテーブル内のデータの状態を正確に表すことを信頼することはできません。ただし、将来的に追加および変更されたデータをチェックすることは信頼できます。
さらに、信頼されていない制約はクエリオプティマイザーによって無視されます。
チェック制約と外部キー制約を有効にするコードはかなり悪く、Wordの「チェック」の3つの意味があります。
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".
WITH NOCHECK
は、定義された制約に準拠していないテーブルに既存のデータがあり、実装している新しい制約に反して実行したくない場合にも使用されます...
WITH CHECK
は確かにデフォルトの動作ですが、コーディング内に含めることをお勧めします。
代替の動作は、もちろんWITH NOCHECK
を使用することなので、意図を明示的に定義することをお勧めします。これは、インラインパーティションで遊んだり、変更したり、切り替えたりするときによく使用されます。
外部キーとチェックの制約には、有効または無効になるだけでなく、信頼される、または信頼されないという概念があります。詳細については、ALTER TABLE
のMSDNページを参照してください。
WITH CHECK
は新しい外部キーとチェック制約を追加するためのデフォルトです、WITH NOCHECK
は無効化された外部キーとチェック制約を再度有効にするためのデフォルトです。違いに注意することが重要です。
そうは言っても、ユーティリティによって生成された明らかに冗長なステートメントは、安全性および/またはコーディングの容易さのために存在します。それらについて心配しないでください。
以下は、データベース内の信頼できない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;