由于多年前一位已离职的开发人员对数据建模师进行了设置监督,我有一个完整的数据库,其中包含在 PK 上具有非聚集索引且没有聚集索引的表。我发现了一篇 StackOverflow 文章,其中包含 FK 丢弃的代码,丢弃了 PK 约束;将 PK 约束重新添加为聚集索引,然后重新添加 FK:Change a Primary Key from Nonclustered to Clustered 我修改了该代码以在其上合并几个出色的 StackOverflow cmets;让它遍历我的数据库中的所有表(除了我让代码排除的两个表);发现它创建了不受信任的外键(每个 sp_Blitz);解决了这个问题,瞧!下面的代码似乎可以很好地将聚集索引添加到数据库中的所有堆表中 - 如果数据库中的所有表都具有身份字段 PK,但排除的除外。
/* Script to take tables with a primary key but not a clustered index to being tables
with a primary key and a clustered index on the PK field. Foreign keys are dropped and recreated.
Much borrowing from a great piece of code from StackOverflow
What if you have a table or two you don't want a clustered index on?
Lines 38 and 39 are for tables you want excluded from the process. Input your own table names here,
if any.
After it finishes, test the altered db against an unaltered version of the db and check
that it recreated all constraints/FKs. The only diff should be the clustered index vs non-clustered.
Revisions made by Ed Z 10/2019:
Original script processes one table only - I wrapped it in a loop that processes all heap tables in a database.
Original script prints the commands - this script both prints them and runs them. If you want to be safe -
just comment out the 5 exec statements and inspect the printed commands; then run them if you wish.
Original script creates untrusted foreign keys (per sp_Blitz) - this script creates trusted foreign keys.
*/
SET NOCOUNT ON;
DECLARE @PKTableName VARCHAR(100),
@PKName varchar(100),
@FKName varchar(100),
@sql varchar(max),
@PKcolumnName varchar(30),
@table VARCHAR(100),
@FKColumnName VARCHAR(100)
SELECT @PKTableName = ''
-- loop thru all the Heap tables; assumes they have a primary key identity field.
WHILE 1 = 1
BEGIN
select @PKTableName = MIN(so.name)
from sys.indexes si inner join sys.objects so on si.object_id = so.object_id
where so.is_ms_shipped = 0
and si.type_desc = 'Heap'
and so.name not like '<a table name you want excluded>' -- exclude a couple of tables that lack a unique key
and so.name not like '<a table name you want excluded>'
and so.name > @PKTableName
IF @PKTableName is null
BREAK
--initialize
select @PKName = '', @FKName = '', @PKcolumnName = '', @sql = '', @table = '', @FKColumnName = ''
IF EXISTS (SELECT * FROM sys.tables WHERE object_id = OBJECT_ID(N'[dbo].[FKAgainstTableList]'))
BEGIN
DROP TABLE FKAgainstTableList
END
--CREATE TABLE FKAgainstTableList (ForeignKey VARCHAR(30),[Table] VARCHAR(30))
--SET @PKTableName = 'MYTABLE'
set @PKName = (SELECT name FROM sys.indexes WHERE OBJECT_NAME(object_id) = @PKTableName AND is_primary_key = 1)
set @PKcolumnName = (SELECT name FROM sys.columns WHERE OBJECT_NAME(object_id) = @PKTableName AND is_identity =1)
/* OR use, if you are not sure there is only one column in the primary key or for primary keys that are not identity fields:
SELECT @PKcolumnName=column_name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE OBJECTPROPERTY(OBJECT_ID(constraint_name), 'IsPrimaryKey') = 1 AND table_name = @PKTableName
*/
-- PRINT @PKcolumnName -- debug
SELECT OBJECT_NAME(sys.foreign_key_columns.parent_object_id) [Table],sys.columns.name [FKColumnName],sys.foreign_keys.name [FKName]
INTO FKAgainstTableList
FROM sys.foreign_keys INNER JOIN sys.foreign_key_columns
ON sys.foreign_keys.object_id = sys.foreign_key_columns.constraint_object_id
INNER JOIN sys.columns ON sys.columns.object_id = sys.foreign_keys.parent_object_id AND sys.columns.column_id = sys.foreign_key_columns.parent_column_id
WHERE OBJECT_NAME(sys.foreign_keys.referenced_object_id) = @PKTableName
DECLARE table_cur1 CURSOR FOR
SELECT * FROM FKAgainstTableList
PRINT @sql
-------------------------------Disable constraint on FK Tables
OPEN table_cur1
FETCH NEXT FROM table_cur1 INTO @table,@FKColumnName,@FKName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql ='ALTER TABLE '+@table+' DROP CONSTRAINT '+ @FKName
PRINT @sql
EXEC(@sql)
FETCH NEXT FROM table_cur1 INTO @table,@FKColumnName,@FKName
END
CLOSE table_cur1
DEALLOCATE table_cur1
--------------------------------DROP AND recreate CLUSTERED pk
IF EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(@PKTableName) AND name = @PKName)
BEGIN
SET @sql = 'ALTER TABLE '+@PKTableName+' DROP CONSTRAINT '+ @PKName
PRINT @sql
EXEC(@sql)
END
SET @sql = 'ALTER TABLE '+@PKTableName +' ADD CONSTRAINT '+@PKName+' PRIMARY KEY CLUSTERED ('+@PKcolumnName+' ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]'
PRINT(@sql)
EXEC(@sql)
--------------------------------Enable FK constraints on FK tables.
DECLARE table_cur2 CURSOR FOR
SELECT * FROM FKAgainstTableList
OPEN table_cur2
FETCH NEXT FROM table_cur2 INTO @table,@FKColumnName,@FKName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql = 'ALTER TABLE '+@table+' WITH NOCHECK ADD CONSTRAINT '+ @FKName+' FOREIGN KEY(['+@FKColumnName+'])
REFERENCES ['+@PKTableName+'] (['+@PKcolumnName+'])'
PRINT(@sql)
EXEC(@sql)
-- SET @sql = 'ALTER TABLE '+@table+' CHECK CONSTRAINT '+@FKName -- this created untrusted foreign keys
SET @sql = 'ALTER TABLE '+@table+' WITH CHECK CHECK CONSTRAINT '+@FKName -- assumes all FK relations are in order
PRINT(@sql)
EXEC(@sql)
FETCH NEXT FROM table_cur2 INTO @table,@FKColumnName,@FKName
END
CLOSE table_cur2
DEALLOCATE table_cur2
DROP TABLE FKAgainstTableList
END -- end while loop
SET NOCOUNT OFF