【问题标题】:Is there a way to convert a non-clustered index on a Primary Key to a clustered one? (SQL Server 2008)有没有办法将主键上的非聚集索引转换为聚集索引? (SQL Server 2008)
【发布时间】:2010-03-12 21:56:20
【问题描述】:

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

【问题讨论】:

  • 你应该能够在不删除键的情况下删除索引
  • 嗯,我不是数据库方面的专家,但是当我创建主键时,该索引是自动创建的(作为非聚集的)。删除它会给我一个错误。我想把它转换成一个集群。
  • 你正在运行什么命令,你得到了什么错误?

标签: sql-server sql-server-2008


【解决方案1】:

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

SQL Server Management Studio可以为您做到这一点。您可能没有意识到,一张表只能有一个聚集索引,因为聚集索引代表了行的物理顺序;这意味着您首先必须启动聚集索引并关闭关闭 聚集。然后,只有这样,您才能拉出另一个索引并打开集群。

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

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

【讨论】:

    【解决方案2】:

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

    ALTER TABLE dbo.YourTable
    DROP CONSTRAINT PK_YourTable
    

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

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

    【讨论】:

    • 当我尝试这个时,SqlServer 说:Msg 3725, Level 16, State 0, Line 1 约束 'PK_Users' 被表 'MetaDataKeyLibraries' 引用,外键约束 'FK_MetaDataKeyLibraries_Users'。跨度>
    • 顺便说一下,marc_s,在接下来的几周里,我还会阅读一些关于 SqlServer 索引的章节,因为也许我只是没有足够的背景知识。无论如何谢谢你帮助我。
    • 嗯,错误只是说其他表(MetaDataKeyLibraries)正在引用此表。因此,您需要先删除这些表上的 FK 约束,然后才能删除主表上的 PK 约束(当然,一旦您的主表上有集群 PK 后再次重新创建它们)
    【解决方案3】:

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

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

    你需要先清理你的约束:S

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        由于多年前一位已离职的开发人员对数据建模师进行了设置监督,我有一个完整的数据库,其中包含在 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
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-04-05
          • 1970-01-01
          • 2011-06-02
          • 2017-04-01
          • 2011-05-15
          • 2012-06-03
          • 2018-05-08
          • 2012-10-01
          相关资源
          最近更新 更多