无法删除非 PK 索引,因为它在外键约束中被引用

Red*_*ter 4 index sql-server constraint primary-key sql-server-2008-r2

我有一张名为MyTable. 主键是一个名为 的标识 int 列MyTableID。在MyTableID名为的 PK 列上有一个唯一的聚集索引PK_MyTable

我注意到IX_MyTable_MytableID该表上有一个额外的非聚集唯一索引,只有一个 column MyTableID,没有其他包含的列。这个索引显然是多余的,但是当我尝试删除它时,我收到一条错误消息:

The constraint 'IX_MyTable_MyTableID' is being referenced by table 'OtherTable', 
foreign key constraint 'FK__OtherTable__MyTableID__369C23FC'.
Run Code Online (Sandbox Code Playgroud)

为什么 FK 约束依赖于非聚集唯一索引而不是主键约束?如何更新 FK 以使用聚集 PK 索引而不是其他索引?

Aar*_*and 8

因为外键可以指向主键唯一约束,并且创建该外键的人可能在主键存在之前创建了它(或者他们将 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));
Run Code Online (Sandbox Code Playgroud)

事实上,这两个外键都将指向在该列 ( )上定义的第一个唯一约束myx

您可以通过删除并重新创建它来修复另一个表上的外键。您将需要对指向此列的任何其他表重复该过程。您可以轻松找到这些:

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');
Run Code Online (Sandbox Code Playgroud)

结果:

dbo    OtherTable1    FK__OtherTable1__ID__32E0915F
dbo    OtherTable2    FK__OtherTable2__ID__35BCFE0A
Run Code Online (Sandbox Code Playgroud)

甚至生成一个脚本来删除和重新创建它们(同时删除冗余的唯一约束):

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;
Run Code Online (Sandbox Code Playgroud)

结果:

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);
Run Code Online (Sandbox Code Playgroud)

这明确处理了这种情况,其中约束仅涉及单个列。如果涉及多个列,它会变得更加复杂(并且此答案并非旨在解决该问题)。如果外键指向一个冗余的唯一索引(它具有相同的底层结构,但使用稍微不同的 DDL 创建),我也没有测试这是否完全按照编码工作。为读者练习。:-)