2009-02-12 4 views
0

[Это немного необычной проблемой, я знаю ...]Массовое изменение уникальных идентификаторов в SQL Server

Что мне нужно, это скрипт, который будет меняться каждый уникальное значение идентификатора нового в нашей базе , Проблема заключается в том, что у нас есть таблицы конфигурации, которые могут быть экспортированы между экземплярами нашего программного обеспечения, которое является id-чувствительным (clobbering existing ids). Несколько лет назад мы установили «достаточно широкий» пробел в идентификаторе между нашей стандартной «стандартной конфигурацией» и экземплярами нашего клиента, которая теперь не достаточно широка :((например, мы получаем конфликты с идентификаторами, когда клиенты импортируют нашу стандартную конфигурацию

Скрипт sql для выполнения следующего, безусловно, является самым простым/самым коротким таймфреймом, который мы можем сделать, например, исправление кода слишком сложное и подверженное ошибкам рассмотрение. Обратите внимание, что мы не «устраняем» проблему . здесь Просто изменяя зазор от 1000-х до 1000000-х или более (существующий разрыв Потребовалось 5 лет, чтобы заполнить)

Я считаю, самым простым решением было бы:.

  • изменить все наши таблицы в UPDATE_CASCADE (ни один из них не являются - это значительно упростит сценарий)
  • создать таблицу идентичности с новым наименьшим идентификатором, который мы хотим
  • Для каждой таблицы, изменять идентификатор в следующий в таблице идентификации (при необходимости, используя флаги модификатора вставки идентификатора). Возможно, после обработки каждой таблицы мы можем сбросить таблицу идентификации.
  • отключите UPDATE_CASCADE и удалите таблицу идентификации.

Есть ли у кого-нибудь скрипты для этого (или частичные скрипты?). Я буду использовать все полезные :).

ответ

0

Здесь мы переходим к коду для SQL 2005. Он огромен, он взломан, но он будет работать (за исключением случая, когда у вас есть первичный ключ, который является составной частью двух других первичных ключей).

Если кто-то может переписать это с добавлением более быстрого идентификатора MrTelly (который не требует создания sql из курсора для каждой обновленной строки), тогда я отмечу это как принятый ответ. (Если я не замечаю новый ответ, upvote это - то я буду замечать :))

BEGIN TRAN 
SET NOCOUNT ON; 

DECLARE @newLowId INT 
SET @newLowId = 1000000 

DECLARE @sql VARCHAR(4000) 

--**** SELECT ALL TABLES WITH IDENTITY COLUMNS **** 
DECLARE tables SCROLL CURSOR 
FOR 
SELECT '[' + SCHEMA_NAME(schema_id) + '].[' + t.name + ']', c.name 
FROM sys.identity_columns c 
INNER JOIN sys.objects t 
    on c.object_id = t.object_id 
WHERE t.type_Desc = 'USER_TABLE' 

OPEN tables 

DECLARE @Table VARCHAR(100) 
DECLARE @IdColumn VARCHAR(100) 

CREATE Table #IdTable(
    id INT IDENTITY(1,1), 
    s CHAR(1) 
) 

FETCH FIRST FROM tables 
INTO @Table, @IdColumn 

WHILE @@FETCH_STATUS = 0 
BEGIN 
    PRINT(' 
****************** '[email protected]+' ****************** 
') 
    --Reset the idtable to the 'low' id mark - remove this line if you want all records to have distinct ids across the database 
    DELETE FROM #IdTable 
    DBCC CHECKIDENT('#IdTable', RESEED, @newLowId) 

    --**** GENERATE COLUMN SQL (for inserts and deletes - updating identities is not allowed) **** 
    DECLARE tableColumns CURSOR FOR 
     SELECT column_name FROM information_schema.columns 
     WHERE '[' + table_schema + '].[' + table_name + ']' = @Table 
     AND column_name <> @IdColumn 
    OPEN tableColumns 
    DECLARE @columnName VARCHAR(100) 
    DECLARE @columns VARCHAR(4000) 
    SET @columns = '' 
    FETCH NEXT FROM tableColumns INTO @columnName 
    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     SET @columns = @columns + @columnName 
     FETCH NEXT FROM tableColumns INTO @columnName 
     IF @@FETCH_STATUS = 0 SET @columns = @columns + ', ' 
    END 

    CLOSE tableColumns 
    DEALLOCATE tableColumns 

    --**** GENERATE FOREIGN ROW UPDATE SQL **** 
    DECLARE foreignkeys SCROLL CURSOR 
    FOR 
    SELECT con.name, 
     '[' + SCHEMA_NAME(f.schema_id) + '].[' + f.name + ']' fTable, fc.column_name , 
     '[' + SCHEMA_NAME(p.schema_id) + '].[' + p.name + ']' pTable, pc.column_name 
    FROM sys.foreign_keys con 
    INNER JOIN sysforeignkeys syscon 
     ON con.object_id = syscon.constid 
    INNER JOIN sys.objects f 
     ON con.parent_object_id = f.object_id 
    INNER JOIN information_schema.columns fc 
     ON fc.table_schema = SCHEMA_NAME(f.schema_id) 
     AND fc.table_name = f.name 
     AND fc.ordinal_position = syscon.fkey 

    INNER JOIN sys.objects p 
     ON con.referenced_object_id = p.object_id 
    INNER JOIN information_schema.columns pc 
     ON pc.table_schema = SCHEMA_NAME(p.schema_id) 
     AND pc.table_name = p.name 
     AND pc.ordinal_position = syscon.rkey 
    WHERE '[' + SCHEMA_NAME(p.schema_id) + '].[' + p.name + ']' = @Table 

    OPEN foreignkeys 

    DECLARE @FKeyName VARCHAR(100) 
    DECLARE @FTable VARCHAR(100) 
    DECLARE @FColumn VARCHAR(100) 
    DECLARE @PTable VARCHAR(100) 
    DECLARE @PColumn VARCHAR(100) 

    --**** RE-WRITE ALL IDS IN THE TABLE **** 
    SET @sql='DECLARE tablerows CURSOR FOR 
    SELECT CAST('[email protected]+' AS VARCHAR) FROM '[email protected]+' ORDER BY '[email protected] 
    PRINT(@sql) 
    exec(@sql) 

    OPEN tablerows 
    DECLARE @rowid VARCHAR(100) 
    DECLARE @id VARCHAR(100) 


    FETCH NEXT FROM tablerows INTO @rowid 
    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     --generate new id 
     INSERT INTO #IdTable VALUES ('') 
     SELECT @id = CAST(@@IDENTITY AS VARCHAR) 
     IF @rowId <> @Id 
     BEGIN 
      PRINT('Modifying '[email protected]+': changing '[email protected]+' to '[email protected]) 
      SET @sql='SET IDENTITY_INSERT ' + @Table + ' ON 
    INSERT INTO '[email protected]+' ('[email protected]+','[email protected]+') SELECT '[email protected]+','[email protected]+' FROM '[email protected]+' WHERE '[email protected]+'='[email protected] 

      --Updating all foreign rows... 
      FETCH FIRST FROM foreignkeys 
      INTO @FKeyName, @FTable, @FColumn, @PTable, @PColumn 

      WHILE @@FETCH_STATUS = 0 
      BEGIN 
       SET @sql = @sql + ' 
    UPDATE '[email protected]+' SET '[email protected]+'='[email protected]+' WHERE '[email protected]+' ='[email protected] 
       FETCH NEXT FROM foreignkeys 
       INTO @FKeyName, @FTable, @FColumn, @PTable, @PColumn 
      END 
      SET @[email protected] + ' 
    DELETE FROM '[email protected]+' WHERE '[email protected]+'='[email protected] 

      PRINT(@sql) 
      exec(@sql) 
     END 
     FETCH NEXT FROM tablerows INTO @rowid 
    END 

    CLOSE tablerows 
    DEALLOCATE tablerows 
    CLOSE foreignkeys 
    DEALLOCATE foreignkeys 

    --Revert to normal identity operation - update the identity to the latest id... 
    DBCC CHECKIDENT(@Table, RESEED, @@IDENTITY) 
    SET @sql='SET IDENTITY_INSERT ' + @Table + ' OFF' 
    PRINT(@sql) 
    exec(@sql) 

    FETCH NEXT FROM tables 
    INTO @Table, @IdColumn 
END 

CLOSE tables 
DEALLOCATE tables 

DROP TABLE #IdTable 
--COMMIT 
--ROLLBACK 
-1

Почему вы не используете отрицательные числа для своих стандартных значений конфигурации и продолжаете использовать положительные числа для других вещей?

+0

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

1

К сожалению, UPDATE_CASCADE не существует в мире Sql Server. Я предлагаю для каждой таблицы вы перепечатывают сделать следующее (псевдокод)

BACKUP DATABASE 
CHECK BACKUP WORKS! 

FOR EACH TABLE TO BE RE-KEYED 
    DROP ALL FOREIGN KEY CONSTRAINTS, INDEXES ETC FROM TABLE 

    SELECT ID + Number, ALL_OTHER_FIELDS INTO TEMP_TABLE FROM TABLE 
    RENAME TABLE OLD_TABLE 
    RENAME TEMP_TABLE TABLE 

    FOR ALL TABLES REFERENCING THIS TABLE 
     UPDATE FOREIGN_KEY_TABLE SET FK_ID = FK_ID + new number 
    END FOR 

    RE-APPLY FOREIGN KEY CONSTRAINTS, INDEXES ETC FROM TABLE 

END FOR 

Проверьте это все еще работает ...

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

+0

Это на самом деле довольно удивительно - просто смещайте id.Мое решение курсирует по всем строкам и обновляет их. Очень утомительно и медленно, но это всего лишь несколько тысяч строк ... – Stephen

+0

Но обратите внимание, что это не сработает, потому что IDENTITY_INSERT работает только для вставок, а не обновлений. Обновления включают ADD и DELETE. – Stephen

+0

Изменен код для использования SELECT INTO, а не UPDATE - я должен был сделать это сам, но я забыл, что получил. – MrTelly

Смежные вопросы