有没有办法将主键上的非聚集索引转换为聚簇索引? (SQL Server 2008)

时间:2010-03-12 21:56:21

标签: sql-server sql-server-2008

使用Management Studio,我无法更改索引。删除它不起作用,因为它被用作许多其他表中的外键。我可以以某种方式改变它吗?或者你会怎么做?

5 个答案:

答案 0 :(得分:20)

如果您的现有PK被许多其他表引用,那么您将花费许多繁琐且容易出错的时间来编写脚本以删除所有FK引用并重新创建它们。

SQL Server Management Studio 可以为您执行此操作。您可能没有意识到的是,您只能在表上有一个聚簇索引,因为聚簇索引表示行的物理顺序;这意味着您首先必须调出聚簇索引并关闭 off 群集。然后,只有这样,你才可以拉出另一个索引并在聚类上转

您需要从表设计器执行此操作,然后右键单击并选择Indexes/Keys...。首先找到现有的聚簇索引(可能是主键),然后将Create as Clustered更改为。然后转到其他索引并将Create as Clustered更改为。如果表格很大,则在保存时操作可能会超时;您可以通过让SSMS生成更改脚本来解决这个问题(在更改索引后右键单击设计器,您将看到该选项)。然后,您可以在查询窗口中运行此脚本,不会超时。

如果您查看此更改脚本,您将看到它正在创建临时表和切换密钥的所有工作;手动编写这个很痛苦。让SSMS为你做。

答案 1 :(得分:8)

您无法就地转换它 - 您需要首先删除主键约束(这也会自动删除主键约束后面的非聚簇索引),然后将其重新创建为聚簇索引:

ALTER TABLE dbo.YourTable
DROP CONSTRAINT PK_YourTable

然后将其重新创建为群集:

ALTER TABLE dbo.YourTable
ADD CONSTRAINT PK_YourTable
PRIMARY KEY CLUSTERED (YourPKField)

答案 2 :(得分:3)

您实际上可以更改非群集PK并使用此语法使其群集

CREATE UNIQUE CLUSTERED INDEX [PK_Customer] on Customer(CustomerID) WITH DROP_EXISTING

您需要先清理约束:S

答案 3 :(得分:1)

您必须删除FK约束,更改PK,然后在新PK到位后重新创建FK约束。外键约束要求引用的键是唯一的,根据定义,主键是唯一的,因此在引用PK的FK约束时不允许丢弃PK。

更改为聚簇索引会重写该表。每当我考虑移入/移出聚簇索引或更改聚簇索引时,我都会重新考虑聚类索引的必要性和选择 - 特别是如果它不是唯一的或增加的(如时间戳的日期时间) 。请记住,聚集索引首先不是真正的索引 - 它是页面中数据的顺序。这就是为什么在向表中添加数据时,对增加键进行聚类有助于页面拆分。

答案 4 :(得分:0)

由于几年前一位久违的开发人员对数据建模人员进行了监督,因此我拥有一个完整的数据库,其中充满了在PK上具有非聚集索引并且没有聚集索引的表。我发现了一篇StackOverflow文章,其中包含执行FK删除,删除PK约束的代码。重新添加了PK约束作为聚簇索引,然后重新添加了FK:Change a Primary Key from Nonclustered to Clustered我修改了该代码以在其上合并一些出色的StackOverflow注释;使它遍历数据库中的所有表(除了我将代码排除在外的两个表之外);发现它创建了不可信的外键(每个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