私はこの問題に何度か遭遇し、主キーの制約を受けて一意のインデックスに変換できるストアドプロシージャを作成することになりました。問題のテーブルを参照するものをすべて削除して再作成します(これは、高度に参照されるテーブルのトリッキーな部分です)。
マルチパートの主キーを処理できます。
CREATE PROCEDURE spPrimaryKeyToUniqueKey
@schema VARCHAR(255),
@tableName VARCHAR(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @constraintsToDrop NVARCHAR(MAX) = '';
DECLARE @constraintsToCreate NVARCHAR(MAX) = '';
DECLARE @indexesToDrop NVARCHAR(MAX) = '';
DECLARE @indexesToCreate NVARCHAR(MAX) = '';
;WITH _fks AS (
SELECT
FkName = fk.[name]
, SchemaName = sch.[name]
, TableName = tab1.[name]
, ColumnName = col1.[name]
, ReferencedSchema = sch2.[name]
, ReferencedTableName = tab2.[name]
, ReferencedColumnName = col2.[name]
, ReferencedColOrder = ic.key_ordinal
FROM sys.foreign_key_columns fkc
JOIN sys.foreign_keys fk ON fk.object_id = fkc.constraint_object_id
JOIN sys.tables tab1 ON tab1.object_id = fkc.parent_object_id
JOIN sys.schemas sch ON tab1.schema_id = sch.schema_id
JOIN sys.columns col1 ON col1.column_id = parent_column_id AND col1.object_id = tab1.object_id
JOIN sys.tables tab2 ON tab2.object_id = fkc.referenced_object_id
JOIN sys.schemas sch2 ON sch2.schema_id = tab2.schema_id
JOIN sys.columns col2 ON col2.column_id = fkc.referenced_column_id AND col2.object_id = tab2.object_id
JOIN sys.indexes i ON fk.key_index_id = i.index_id AND i.object_id = tab2.object_id
JOIN sys.index_columns ic ON col2.column_id = ic.column_id AND ic.object_id = tab2.object_id AND i.index_id = ic.index_id
)
, _fksWithColList AS (
SELECT
f.FkName
, f.SchemaName
, f.TableName
, f.ReferencedSchema
, f.ReferencedTableName
, TableColNameList = STUFF(
(SELECT ',[' + ff.ColumnName + ']'
FROM _fks ff
WHERE f.FkName = ff.FkName AND f.SchemaName = ff.SchemaName AND f.TableName = ff.TableName AND f.ReferencedSchema = ff.ReferencedSchema AND f.ReferencedTableName = ff.ReferencedTableName
ORDER BY ff.ReferencedColOrder
FOR XML PATH('')), 1, 1, '')
, ReferencedTableColNameList = STUFF(
(SELECT ',[' + ff.ReferencedColumnName + ']'
FROM _fks ff
WHERE f.FkName = ff.FkName AND f.SchemaName = ff.SchemaName AND f.TableName = ff.TableName AND f.ReferencedSchema = ff.ReferencedSchema AND f.ReferencedTableName = ff.ReferencedTableName
ORDER BY ff.ReferencedColOrder
FOR XML PATH('')), 1, 1, '')
FROM _fks f
GROUP BY f.FkName, f.SchemaName, f.TableName, f.ReferencedSchema, f.ReferencedTableName
)
, _commands AS (
SELECT *,
DropStatement = REPLACE(REPLACE(REPLACE('ALTER TABLE [{0}].[{1}] DROP CONSTRAINT IF EXISTS [{2}];',
'{0}', SchemaName),
'{1}', TableName),
'{2}', FkName),
CreateStatement = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE('ALTER TABLE [{0}].[{1}] DROP CONSTRAINT IF EXISTS [{2}]; ALTER TABLE [{0}].[{1}] ADD CONSTRAINT [{2}] FOREIGN KEY ({3}) REFERENCES [{4}].[{5}]({6});',
'{0}', SchemaName),
'{1}', TableName),
'{2}', FkName),
'{3}', TableColNameList),
'{4}', ReferencedSchema),
'{5}', ReferencedTableName),
'{6}', ReferencedTableColNameList)
FROM _fksWithColList
WHERE ReferencedSchema = @schema AND ReferencedTableName = @tableName
)
SELECT @constraintsToDrop = @constraintsToDrop + DropStatement + CHAR(13)+CHAR(10), @constraintsToCreate = @constraintsToCreate + c.CreateStatement + CHAR(13)+CHAR(10)
FROM _commands c
;WITH _indexes AS (
SELECT
TableName = t.[name]
, SchemaName = s.[name]
, IndexName = i.[name]
, ColumnName = c.[name]
, ColOrder = ic.key_ordinal
, ColDesc = ic.is_descending_key
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
JOIN sys.indexes i ON t.object_id = i.object_id
JOIN sys.columns c ON t.object_id = c.object_id
JOIN sys.index_columns ic ON t.object_id = ic.object_id AND c.column_id = ic.column_id AND i.index_id = ic.index_id
WHERE s.[name] = @schema AND t.[name] = @tableName AND i.is_primary_key = 1
)
, indexesWithColList AS (
SELECT
TableName
, SchemaName
, IndexName
, IndexColList = STUFF(
(SELECT ',[' + ii.ColumnName + ']' + IIF(ii.ColDesc = 1, ' DESC', '')
FROM _indexes ii
WHERE i.TableName = ii.TableName AND i.SchemaName = ii.SchemaName AND i.IndexName = ii.IndexName AND ii.ColOrder > 0
ORDER BY ii.ColOrder
FOR XML PATH('')), 1, 1, '')
, IndexColNameList = STUFF(
(SELECT '_' + ii.ColumnName
FROM _indexes ii
WHERE i.TableName = ii.TableName AND i.SchemaName = ii.SchemaName AND i.IndexName = ii.IndexName AND ii.ColOrder > 0
ORDER BY ii.ColOrder
FOR XML PATH('')), 1, 1, '')
FROM _indexes i
GROUP BY i.TableName, i.SchemaName, i.IndexName
)
, _commands AS (
SELECT *,
DropStatement = REPLACE(REPLACE(REPLACE('ALTER TABLE [{0}].[{1}] DROP CONSTRAINT {2};',
'{0}', i.SchemaName),
'{1}', i.TableName),
'{2}', i.IndexName),
CreateStatement = REPLACE(REPLACE(REPLACE(REPLACE('CREATE UNIQUE NONCLUSTERED INDEX [{0}] ON [{1}].[{2}] ({3});',
'{0}', 'UX_' + i.SchemaName + '_' + i.TableName + '_' + i.IndexColNameList),
'{1}', i.SchemaName),
'{2}', i.TableName),
'{3}', i.IndexColList)
FROM indexesWithColList i
)
SELECT @indexesToDrop = @indexesToDrop + c.DropStatement + CHAR(13)+CHAR(10), @indexesToCreate = @indexesToCreate + c.CreateStatement + CHAR(13)+CHAR(10)
FROM _commands c
DECLARE @sql NVARCHAR(MAX);
SET @sql = REPLACE(REPLACE(REPLACE(REPLACE('
SET XACT_ABORT ON;
BEGIN TRAN t1
--Drop foreign keys
{0}
--Drop indexes
{1}
--Create indexes
{2}
--Recreate foreign keys
{3}
COMMIT TRAN t1
',
'{0}', @constraintsToDrop),
'{1}', @indexesToDrop),
'{2}', @indexesToCreate),
'{3}', @constraintsToCreate);
PRINT @sql;
EXEC sp_executesql @sql
END
GO