在更改列数据类型时保留SQL索引

时间:2009-08-11 03:45:51

标签: sql sql-server tsql

我有一个smalldatetime列,我需要将其更改为datetime列。这是安装过程的一部分,因此它不能是手动过程。不幸的是,该列有一些索引和一个非空约束。索引与性能相关,只需使用新数据类型即可保留。是否有可能编写一个语句,允许我在更改列数据类型的同时保留相关信息?如果是这样,怎么办呢?

4 个答案:

答案 0 :(得分:12)

您无法使用索引,唯一约束,外键约束或检查约束将数据类型从smalldatetime更改为datetime。在更改类型之前,您必须将它们全部丢弃。然后:

alter table T alter column TestDate datetime not null

然后重新创建仍然适用的约束和索引。


生成drop的一些不同方法并创建:

1)如果您已为所有索引和约束指定了明确的名称,那么您的安装程序可以在每个环境中运行静态脚本(开发,测试,用户验收测试,性能测试等,生产。)

要生成此显式脚本,您可以: a)使用SSMS(或SQL Server 2000,企业管理器)编写create和drop语句的脚本。 b)从您的源代码存储库开始工作,以发现依赖对象的名称和定义,并将相应的静态脚本放在一起。 c)尝试运行alter语句。看看它失败了。查找定义并手写drop和create。 (就个人而言,这对于编写drop而言非常有用,但在创建时并不是很好。)

2)如果您没有为所有索引和约束指定明确的名称,那么安装程序必须在数据字典中查询相应的名称,并使用动态SQL以正确的顺序运行drop,然后再进行更改列语句,然后在更改列之后以正确的顺序创建。

如果您知道没有约束,只有索引,这将更简单。

可能有工具或库已经知道如何执行此操作。

此外,如果这是打包的应用程序,您可能无法确保本地DBA没有添加索引。

注意:如果存在唯一约束,它将构建一个索引,您将无法使用DROP INDEX删除该索引。

答案 1 :(得分:7)

如果您只是更改尺寸,索引仍将保留在桌面上。

如果要更改数据类型,则会收到一条错误消息,指出对象依赖于您尝试更改的列,因此您将无法更改它。

您可以手动或通过脚本编写有问题的索引。在SSMS中,右键单击该表并编写相关对象的脚本。

如果你想要编程索引脚本,这里是我一直使用的存储过程,我是从我的一位前同事那里得到的。

Drop Proc ScriptIndex
GO
Create Proc ScriptIndex
    @TableName      VarChar (Max),
    @IndexScript    VarChar (Max) OUTPUT
AS

-- Get all existing indexes, EXCEPT the primary keys
DECLARE cIX CURSOR FOR
SELECT OBJECT_NAME(SI.Object_ID), SI.Object_ID, SI.Name, SI.Index_ID
FROM Sys.Indexes SI 
    LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC 
        ON SI.Name = TC.CONSTRAINT_NAME 
        AND OBJECT_NAME(SI.Object_ID) = TC.TABLE_NAME
WHERE 1=1
    AND OBJECT_NAME(SI.Object_ID) = @TableName
    AND TC.CONSTRAINT_NAME IS NULL
    AND OBJECTPROPERTY(SI.Object_ID, 'IsUserTable') = 1
ORDER BY OBJECT_NAME(SI.Object_ID), SI.Index_ID

DECLARE @IxTable SYSNAME
DECLARE @IxTableID INT
DECLARE @IxName SYSNAME
DECLARE @IxID INT

-- Loop through all indexes
OPEN cIX
FETCH NEXT FROM cIX INTO @IxTable, @IxTableID, @IxName, @IxID
WHILE (@@FETCH_STATUS = 0)
BEGIN
   DECLARE @IXSQL NVARCHAR(4000) 
   DECLARE @PKSQL NVARCHAR(4000) 
   SET @PKSQL = ''
   SET @IXSQL = 'CREATE '

   -- Check if the index is unique
   IF (INDEXPROPERTY(@IxTableID, @IxName, 'IsUnique') = 1)
      SET @IXSQL = @IXSQL + 'UNIQUE '
   -- Check if the index is clustered
   IF (INDEXPROPERTY(@IxTableID, @IxName, 'IsClustered') = 1)
      SET @IXSQL = @IXSQL + 'CLUSTERED '

   SET @IXSQL = @IXSQL + 'INDEX ' + @IxName + ' ON ' + @IxTable + '('

   -- Get all columns of the index
   DECLARE cIxColumn CURSOR FOR 
      SELECT SC.Name
      FROM Sys.Index_Columns IC
         JOIN Sys.Columns SC ON IC.Object_ID = SC.Object_ID AND IC.Column_ID = SC.Column_ID
      WHERE IC.Object_ID = @IxTableID AND Index_ID = @IxID
      ORDER BY IC.Index_Column_ID

   DECLARE @IxColumn SYSNAME
   DECLARE @IxFirstColumn BIT SET @IxFirstColumn = 1

   -- Loop throug all columns of the index and append them to the CREATE statement
   OPEN cIxColumn
   FETCH NEXT FROM cIxColumn INTO @IxColumn
   WHILE (@@FETCH_STATUS = 0)
   BEGIN
      IF (@IxFirstColumn = 1)
         SET @IxFirstColumn = 0
      ELSE
         SET @IXSQL = @IXSQL + ', '

      SET @IXSQL = @IXSQL + @IxColumn

      FETCH NEXT FROM cIxColumn INTO @IxColumn
   END
   CLOSE cIxColumn
   DEALLOCATE cIxColumn

   SET @IXSQL = @IXSQL + ')'
   -- Print out the CREATE statement for the index
   PRINT @IXSQL

   FETCH NEXT FROM cIX INTO @IxTable, @IxTableID, @IxName, @IxID
END

CLOSE cIX
DEALLOCATE cIX

GO
Declare @TableName VarChar (Max), @IndexScript VarChar (Max)

Exec ScriptIndex 'Client', @IndexScript OUTPUT
Print @IndexScript

答案 2 :(得分:4)

编辑:这取决于原始和更改的数据类型。 如果您尝试将列从varchar更改为nvarchar,则会失败。 然而,如果您将列从varchar(16)更改为varchar(32),它将成功。

--Disable Index
ALTER INDEX MyIndex ON MyTable DISABLE
GO

-- Change column datatype

--Enable Index
ALTER INDEX MyIndex ON MyTable REBUILD
GO

如果更改列的类型,则必须重建使用该列的所有索引。

但除非您拥有大量数据(或全天候运行),否则重建索引并不是什么大问题。只需安排一个维护窗口。

答案 3 :(得分:0)

最好的办法是创建一个返回给定表/列的索引脚本的过程。因此,您可以仅从正在更改的列中删除索引,而不是从表中删除所有索引,而创建索引可能有些昂贵。

  1. 将过程结果存储在数据表
  2. 删除列的索引
  3. 修改列
  4. 重建存储在数据表中的索引

    -- objective   : Generates indices scripting using specified column
    -- Parameters : 
    --     @Tabela  -> Name of the table that the column belongs to 
    --     @Coluna -> Name of the column that will be searched for the indices to generate the script
    --Use: proc_ScriptIndexColumn 'TableName', 'CollumnName'
    
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    Create Proc proc_ScriptIndexColumn (@Tabela VARCHAR(4000), @Coluna VARCHAR(4000)) 
    AS 
    BEGIN     
         DECLARE @isql_key VARCHAR(4000), 
             @isql_incl VARCHAR(4000), 
             @tableid INT, 
             @indexid INT         
     DECLARE @tablename VARCHAR(4000), 
             @indexname VARCHAR(4000)         
     DECLARE @isunique INT, 
             @isclustered INT, 
             @indexfillfactor INT         
     DECLARE @srsql VARCHAR(MAX)        
     DECLARE @ScriptsRetorno TABLE 
             (Script VARCHAR(MAX))        
     DECLARE index_cursor CURSOR  
       FOR 
         SELECT tablename = OBJECT_NAME(i.[object_id]), 
                tableid       = i.[object_id], 
                indexid       = i.index_id, 
                indexname     = i.name, 
                isunique      = i.is_unique, 
                CASE I.type_desc 
                     WHEN 'CLUSTERED' THEN 1 
                     ELSE 0 
                END                     AS isclustered, 
                indexfillfactor = i.fill_factor                  
         FROM   sys.indexes             AS i 
                INNER JOIN SYSOBJECTS   AS O 
                     ON  I.[object_id] = O.ID 
                INNER JOIN sys.index_columns AS ic 
                     ON  (ic.column_id > 0 
                             AND (ic.key_ordinal > 0 
                                     OR ic.partition_ordinal = 0 
                                     OR ic.is_included_column != 0 
                                 )) 
                     AND (   ic.index_id = CAST(i.index_id AS INT) 
                             AND ic.object_id = i.[object_id] 
                         ) 
                INNER JOIN sys.columns  AS sc 
                        ON  sc.object_id = ic.object_id 
                     AND sc.column_id = ic.column_id 
         WHERE  O.XTYPE = 'U' 
                AND i.typE = 2 /*Non clustered*/ 
                AND i.is_unique = 0 
                AND i.is_hypothetical = 0 
                AND UPPER(OBJECT_NAME(i.[object_id])) = UPPER(@Tabela) 
                AND UPPER(sc.name) = UPPER(@Coluna)       
    
     OPEN index_cursor  
     FETCH NEXT FROM index_cursor INTO @tablename,@tableid, @indexid,@indexname ,  
     @isunique ,@isclustered , @indexfillfactor       
     WHILE @@fetch_status <> -1 
     BEGIN 
         SELECT @isql_key = '', 
                @isql_incl = ''           
         SELECT @isql_key = CASE ic.is_included_column 
                                 WHEN 0 THEN CASE ic.is_descending_key 
                                                  WHEN 1 THEN @isql_key +COALESCE(sc.name, '') + 
                                                       ' DESC, ' 
                                                  ELSE @isql_key + COALESCE(sc.name, '')  
                                                       + ' ASC, ' 
                                             END 
                                 ELSE @isql_key 
                             END, 
                --include column  
                @isql_incl = CASE ic.is_included_column 
                                  WHEN 1 THEN CASE ic.is_descending_key 
                                                   WHEN 1 THEN @isql_incl + 
                                                        COALESCE(sc.name, '') + 
                                                        ', ' 
                                                   ELSE @isql_incl + COALESCE(sc.name, '')  
                                                        + ', ' 
                                              END 
                                  ELSE @isql_incl 
                             END 
         FROM   sysindexes i 
                INNER JOIN sys.index_columns AS ic 
                     ON  ( 
                             ic.column_id > 0 
                             AND ( 
                                     ic.key_ordinal > 0 
                                     OR ic.partition_ordinal = 0 
                                     OR ic.is_included_column != 0 
                                 ) 
                         ) 
                     AND (ic.index_id = CAST(i.indid AS INT) AND ic.object_id = i.id) 
                INNER JOIN sys.columns AS sc 
                       ON  sc.object_id = ic.object_id 
                     AND sc.column_id = ic.column_id 
         WHERE  i.indid > 0 
                AND i.indid < 255 
                AND (i.status & 64) = 0 
                AND i.id = @tableid 
                AND i.indid = @indexid 
         ORDER BY 
                i.name, 
                CASE ic.is_included_column 
                     WHEN 1 THEN ic.index_column_id 
                     ELSE ic.key_ordinal 
                END           
         IF LEN(@isql_key) > 1 
             SET @isql_key = LEFT(@isql_key, LEN(@isql_key) -1)  
    
         IF LEN(@isql_incl) > 1 
             SET @isql_incl = LEFT(@isql_incl, LEN(@isql_incl) -1)            
         SET @srsql = 'CREATE ' + 'INDEX [' + @indexname + ']' + ' ON [' + @tablename 
             + '] '           
         SET @srsql = @srsql + '(' + @isql_key + ')'              
         IF (@isql_incl <> '') 
             SET @srsql = @srsql + ' INCLUDE(' + @isql_incl + ')'             
         IF (@indexfillfactor <> 0) 
              SET @srsql = @srsql + ' WITH ( FILLFACTOR = ' + CONVERT(VARCHAR(10), @indexfillfactor) 
                 + ')'            
         FETCH NEXT FROM index_cursor INTO @tablename,@tableid,@indexid,@indexname,  
         @isunique ,@isclustered , @indexfillfactor           
         INSERT INTO @ScriptsRetorno 
         VALUES 
           (@srsql) 
     END  
     CLOSE index_cursor  
     DEALLOCATE index_cursor   
     SELECT * 
     FROM   @ScriptsRetorno 
    RETURN @@ERROR 
    END