web-dev-qa-db-ja.com

非PKインデックスは外部キー制約で参照されているため、削除できません

MyTableという名前のテーブルがあります。主キーは、MyTableIDという名前のidentity int列です。 PK列MyTableIDには、PK_MyTableという一意のクラスター化インデックスがあります。

単一の列MyTableIDがあり、他に含まれている列がない、そのテーブルに追加の非クラスター化一意インデックスIX_MyTable_MytableIDがあることに気付きました。このインデックスは明らかに冗長ですが、削除しようとするとエラーメッセージが表示されます。

The constraint 'IX_MyTable_MyTableID' is being referenced by table 'OtherTable', 
foreign key constraint 'FK__OtherTable__MyTableID__369C23FC'.

FK制約が主キー制約の代わりに非クラスター化一意インデックスに依存しているのはなぜですか?他のインデックスの代わりにクラスター化PKインデックスを使用するようにFKを更新するにはどうすればよいですか?

4
RedFilter

外部キーは主キーを指すことができるためor一意の制約であり、その外部キーを作成した人は主キーが存在する前にそれを作成した可能性があります(またはFKをシフトして一意のインデックスを指すようにします)主キーについて何か他のものを変更した)。これは簡単に再現できます。

CREATE TABLE dbo.MyTable(MyTableID INT NOT NULL, CONSTRAINT myx UNIQUE(MyTableID));

CREATE TABLE dbo.OtherTable1(ID INT FOREIGN KEY REFERENCES dbo.MyTable(MyTableID));

ALTER TABLE dbo.MyTable ADD CONSTRAINT PKmyx PRIMARY KEY(MyTableID);

CREATE TABLE dbo.OtherTable2(ID INT FOREIGN KEY REFERENCES dbo.MyTable(MyTableID));

実際、これらの外部キーのbothは、その列(myx)に定義されたfirst一意制約を指します。

他のテーブルの外部キーを削除して再作成することで修正できます。この列を指す他のテーブルについても、このプロセスを繰り返す必要があります。これらは簡単に見つけることができます:

SELECT s.name,t.name,fk.name
FROM sys.foreign_key_columns AS fkc
INNER JOIN sys.foreign_keys AS fk
ON fkc.constraint_object_id = fk.[object_id]
INNER JOIN sys.tables AS t
ON fkc.parent_object_id = t.[object_id]
INNER JOIN sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
INNER JOIN sys.columns AS c1
ON c1.[object_id] = fkc.referenced_object_id
AND c1.column_id = fkc.referenced_column_id
AND c1.name = N'MyTableID'
WHERE fkc.referenced_object_id = OBJECT_ID('dbo.MyTable');

結果:

dbo    OtherTable1    FK__OtherTable1__ID__32E0915F
dbo    OtherTable2    FK__OtherTable2__ID__35BCFE0A

そして、それらを削除して再作成するためのスクリプトを生成します(その間、冗長な一意制約を削除します)。

DECLARE 
  @sql1 NVARCHAR(MAX) = N'', 
  @sql2 NVARCHAR(MAX) = N'ALTER TABLE dbo.MyTable DROP CONSTRAINT myx;', 
  @sql3 NVARCHAR(MAX) = N'';

SELECT 
  @sql1 += N'
ALTER TABLE ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
  + ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';',
  @sql3 += N'
ALTER TABLE ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
  + ' ADD CONSTRAINT ' + QUOTENAME(fk.name) + ' FOREIGN KEY '
  + '(' + QUOTENAME(c2.name) + ') REFERENCES dbo.MyTable(MyTableID);'
FROM sys.foreign_key_columns AS fkc
INNER JOIN sys.foreign_keys AS fk
ON fkc.constraint_object_id = fk.[object_id]
INNER JOIN sys.tables AS t
ON fkc.parent_object_id = t.[object_id]
INNER JOIN sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
INNER JOIN sys.columns AS c1
ON c1.[object_id] = fkc.referenced_object_id
AND c1.column_id = fkc.referenced_column_id
AND c1.name = N'MyTableID'
INNER JOIN sys.columns AS c2
ON c2.[object_id] = fkc.parent_object_id
AND c2.column_id = fkc.parent_column_id
WHERE fkc.referenced_object_id = OBJECT_ID('dbo.MyTable');

PRINT @sql1;
PRINT @sql2;
PRINT @sql3;
-- EXEC sp_executesql @sql1;
-- EXEC sp_executesql @sql2;
-- EXEC sp_executesql @sql3;

結果:

ALTER TABLE [dbo].[OtherTable1] DROP CONSTRAINT [FK__OtherTable1__ID__32E0915F];
ALTER TABLE [dbo].[OtherTable2] DROP CONSTRAINT [FK__OtherTable2__ID__35BCFE0A];

ALTER TABLE dbo.MyTable DROP CONSTRAINT myx;

ALTER TABLE [dbo].[OtherTable1] ADD CONSTRAINT [FK__OtherTable1__ID__32E0915F] 
  FOREIGN KEY ([ID]) REFERENCES dbo.MyTable(MyTableID);
ALTER TABLE [dbo].[OtherTable2] ADD CONSTRAINT [FK__OtherTable2__ID__35BCFE0A] 
  FOREIGN KEY ([ID]) REFERENCES dbo.MyTable(MyTableID);

これは、このケースを明示的に処理します。この場合、制約には単一の列のみが含まれます。複数の列が関係している場合は少し複雑になります(この回答はその問題を解決するためのものではありません)。また、外部キーが冗長な一意のインデックス(基本的な構造は同じですが、わずかに異なるDDLで作成されている)を指している場合、これがコーディングどおりに機能するかどうかもテストしませんでした。読者のための練習。 :-)

8
Aaron Bertrand