Сохранять индексы SQL при изменении типа столбца

У меня есть столбец smalldatetime, который мне нужно изменить, чтобы быть столбцом datetime. Это то, что будет частью процесса установки, поэтому это не может быть ручной процедурой. К сожалению, столбец имеет несколько индексов и не имеет нулевого ограничения. Индексы связаны с производительностью и должны быть сохранены только с использованием нового типа данных. Можно ли написать выражение, которое позволит мне сохранить соответствующую информацию, все еще изменяя тип столбца? Если да, то как это можно сделать?

Ответ 1

Вы не можете изменить тип данных из smalldatetime в datetime с индексами, уникальными ограничениями, ограничениями внешнего ключа или проверить ограничения на месте. Перед изменением типа вам придется отбросить их все. Тогда:

alter table T alter column TestDate datetime not null

Затем заново создайте ограничения и индексы, которые все еще применяются.


Некоторые различные подходы к генерации drop и создают:

1) Если вы указали явные имена для всех индексов и ограничений, ваш установщик может запускать статический script в каждой среде (dev, test, тестирование приёма пользователей, тестирование производительности и т.д.).

Чтобы создать этот явный script, вы можете: a) Используйте SSMS (или с SQL Server 2000, корпоративный менеджер) для script операторов создания и удаления. b) Работайте у вас в репозитории исходного кода, чтобы узнать имена и определения зависимых объектов и соединить соответствующий статический script. c) Попытайтесь запустить инструкцию alter. Посмотрите, что он терпит неудачу. Посмотрите определения и вручную напишите капли и создайте. (Лично это было бы здорово для написания капли, не так хорошо при создании.)

2) Если вы не указали явные имена для всех индексов и ограничений, то ваш установщик должен будет запросить словарь данных для соответствующих имен и использовать динамический SQL для запуска капель в правильном порядке до изменения столбца, а затем создает в правильном порядке после столбца alter.

Это будет проще, если вы знаете, что нет ограничений и просто индексов.

Могут быть инструменты или библиотеки, которые уже знают, как это сделать.

Кроме того, если это пакетное приложение, вы не можете быть уверены, что локальные администраторы баз данных не добавили индексы.

ПРИМЕЧАНИЕ. Если существует уникальное ограничение, он будет иметь индекс, который вы не сможете удалить с помощью DROP INDEX.

Ответ 2

Если вы просто изменяете размер, индекс все равно останется в таблице.

Если вы меняете тип данных, вы получите сообщение об ошибке, указывающее, что объекты зависят от столбца, который вы пытаетесь изменить, и поэтому вы не сможете его изменить.

Вы можете script вывести указанные индексы вручную или через script. В SSMS щелкните правой кнопкой мыши таблицу и script на объект, о котором идет речь.

Если вам нужен программный индексный скриптинг, вот хранимый процесс, который я использовал, который я получил от моего бывшего коллеги.

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

Ответ 3

EDIT. Это зависит от исходного и измененного типа данных. Если вы попытаетесь изменить столбец с 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

Если вы измените тип столбца, все индексы, которые используют этот столбец, должны быть перестроены.

Но если у вас нет огромных объемов данных (или работает 24/7), восстановление индексов не имеет большого значения. Просто запланировать окно обслуживания.

Ответ 4

Лучше всего создать процедуру, которая возвращает индекс script данной таблицы/столбца. Таким образом, вы можете удалить индексы только из измененного столбца, а не всех индексов из таблицы, тогда как создание индексов может быть несколько дорогим.

  • Сохраняет результат процедуры в datatable
  • Удалить индексы столбца
  • Изменить колонку
  • Восстановить индексы, хранящиеся в datatable

    -- 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