Сохранение индексов SQL при изменении типа столбца - PullRequest
16 голосов
/ 11 августа 2009

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

Ответы [ 4 ]

13 голосов
/ 11 августа 2009

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

alter table T alter column TestDate datetime not null

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


Некоторые разные подходы для создания дропа и создания:

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

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

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

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

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

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

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

7 голосов
/ 11 августа 2009

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

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

Вы можете написать соответствующие индексы вручную или с помощью сценария. В 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
4 голосов
/ 11 августа 2009

РЕДАКТИРОВАТЬ : Зависит от исходного и измененного типа данных. Если вы попытаетесь изменить столбец с 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), перестройка индексов не составляет большого труда. Просто запланируйте период обслуживания.

0 голосов
/ 19 июля 2017

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

  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 
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...