SQL Server中唯一ID的批量修改

时间:2009-02-12 21:49:30

标签: sql-server database scripting identity

[这是一个不寻常的问题,我知道......]

我需要的是一个脚本,它会将每个唯一的id值更改为数据库中的新值。问题是我们有配置表可以在我们的软件实例之间导出,这些实例是id敏感的(破坏现有的id)。多年前,我们在开发“标准配置”和客户端实例之间建立了“足够广泛”的ID差距,现在不够足够宽广:(例如,当我们遇到id冲突时客户导入我们的标准配置。

执行以下操作的sql脚本绝对是我们可以做的最简单/最短时间的事情。例如修复代码太复杂,容易出错。请注意,我们并没有“消除”这里的问题。只需将差距从1000转换到1000000或更高(现有差距需要5年才能填补)。

我认为最简单的解决方案是:

  • 将我们所有的表更改为UPDATE_CASCADE(它们都不是 - 这将大大简化脚本)
  • 使用我们想要的新的最低ID创建一个身份表
  • 对于每个表,将id修改为标识表中的下一个(必要时使用标识插入修饰符标记)。也许在处理完每个表之后,我们可以重置身份表。
  • 关闭UPDATE_CASCADE,并删除身份表。

是否有人为此(或部分脚本?)有任何脚本。我会赞成任何有用的东西:)。

3 个答案:

答案 0 :(得分:1)

不幸的是,在Sql Server的世界中不存在UPDATE_CASCADE。我建议你为每个表重新键入你做以下(伪代码)

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

答案 1 :(得分:0)

这里我们使用SQL 2005的代码。它很庞大,很糟糕,但它工作(除非你的主键是两个其他主键的复合键) )。

如果有人可以使用MrTelly更快的id添加(这不需要为每个更新的行从游标构建sql)重写这个,那么我会将其标记为已接受的答案。 (如果我没有注意到新的答案,请注意这一点 - 然后我会注意到:))

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('
****************** '+@Table+' ******************
')
    --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('+@IdColumn+' AS VARCHAR) FROM '+@Table+' ORDER BY '+@IdColumn
    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 '+@Table+': changing '+@rowId+' to '+@id)
            SET @sql='SET IDENTITY_INSERT ' + @Table + ' ON
    INSERT INTO '+@Table+' ('+@IdColumn+','+@columns+') SELECT '+@id+','+@columns+' FROM '+@Table+' WHERE '+@IdColumn+'='+@rowId

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

            WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @sql = @sql + '
    UPDATE '+@FTable+' SET '+@FColumn+'='+@id+' WHERE '+@FColumn+' ='+@rowId
                FETCH NEXT FROM foreignkeys
                INTO @FKeyName, @FTable, @FColumn, @PTable, @PColumn
            END
            SET @sql=@sql + '
    DELETE FROM '+@Table+' WHERE '+@IdColumn+'='+@rowId

            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

答案 2 :(得分:-1)

为什么不使用负数作为标准配置值,并继续使用正数表示其他内容?