SQL Server:将 PK 类型从 uniqueidentifier 更改为 int

问题描述 投票:0回答:3

我设计了一个数据库,所有表的主键类型都是

uniqueidentifier
。它有 50 个表和现有数据。然后,我知道这是一个坏主意。我想从
int
更改为
uniqueidentifier
pk 类型。

我该怎么办?如何移动外键?

sql sql-server sql-server-2014
3个回答
0
投票

要采取的一般步骤是(链接是有关使用 SQL Server 执行这些步骤的 MSDN 信息):

  1. 删除当前主键约束 - 删除主键
  2. 更改表以删除唯一标识符字段并创建新的整数字段 - 更改表
  3. 在新的整数字段上创建新的主键 创建主键

0
投票

刚刚完成了一个脚本,在几个表上进行了测试并且工作正常,在生产环境中执行之前请自行测试。

该脚本执行以下操作。

  1. 查找主键具有数据类型 Uniqueidentifier 的所有列。
  2. 删除主键约束。
  3. 删除“唯一标识符”列。
  4. 添加带有 Identity 的 INT 列,从种子值 1 开始,增量为 1。
  5. 将该列设为该表中的主键列。

declare @table SYSNAME,@Schema SYSNAME 
      , @PkColumn SYSNAME, @ContName SYSNAME
      ,@Sql nvarchar(max)


DECLARE db_cursor CURSOR LOCAL FORWARD_ONLY FOR  
SELECT OBJECT_NAME(O.object_id) AS ConstraintName
    ,SCHEMA_NAME(O.schema_id) AS SchemaName
    ,OBJECT_NAME(O.parent_object_id) AS TableName
    ,c.name ColumName
FROM sys.objects o 
inner join sys.columns c ON o.parent_object_id = c.object_id
inner join sys.types   t ON c.user_type_id = t.user_type_id
WHERE o.type_desc = 'PRIMARY_KEY_CONSTRAINT'
and   t.name = 'uniqueidentifier'

Open db_cursor
fetch next from db_cursor into @ContName , @Schema , @table, @PkColumn

while (@@FETCH_STATUS = 0)
BEGIN

   SET @Sql= 'ALTER TABLE ' + QUOTENAME(@Schema) +'.'+ QUOTENAME(@table) 
              + ' DROP CONSTRAINT ' + QUOTENAME(@ContName)

    Exec sp_executesql @Sql


   SET @Sql= 'ALTER TABLE ' + QUOTENAME(@Schema) +'.'+ QUOTENAME(@table) 
              + ' DROP COLUMN ' + QUOTENAME(@PkColumn)

   Exec sp_executesql @Sql


   SET @Sql= 'ALTER TABLE ' + QUOTENAME(@Schema) +'.'+ QUOTENAME(@table) 
              + ' ADD  ' + QUOTENAME(@PkColumn) 
              + ' INT NOT NULL IDENTITY(1,1) '

   Exec sp_executesql @Sql

  SET @Sql= 'ALTER TABLE  ' + QUOTENAME(@Schema) +'.'+ QUOTENAME(@table) 
           + ' ADD CONSTRAINT '+ QUOTENAME(@table+'_'+ @PkColumn)
           + ' PRIMARY KEY ('+QUOTENAME(@PkColumn)+')'

  Exec sp_executesql @Sql

  fetch next from db_cursor into @ContName , @Schema , @table, @PkColumn

END

Close db_cursor
deallocate db_cursor

0
投票

我们陷入了同样的困境。我们需要集成一个新的供应商包,我们所有的 ROWGUIDCOL 都需要更改为 INT IDENTITY(1,1)

请根据您的需要进行修改

跟随评论看看它是如何工作的

Use <yourDatabase>;

-- varchar to nvarchar

DECLARE @TableName NVARCHAR(255);
DECLARE @ColumnName NVARCHAR(255);
DECLARE @MaxLength varchar(10);
DECLARE @sql NVarchar(max);

DECLARE column_cursor CURSOR FOR

SELECT 
    t.name AS TableName, 
    c.name AS ColumnName, 
    case 
        when c.max_length = -1 then 'MAX' 
        when c.max_length * 2 > 255 then 'MAX' 
        else cast((c.max_length * 2) as varchar(10)) 
    end as Size
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.system_type_id = 167 -- VARCHAR system type ID
AND c.collation_name LIKE '%_CI_AS'
-- AND (t.name like 'BTS_%' or c.name like 'BTS_%') -- specifically filter the tables that you want to update for
OPEN column_cursor;

FETCH NEXT FROM column_cursor INTO @TableName, @ColumnName, @MaxLength;

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @sql = 'ALTER TABLE ' + QUOTENAME(@TableName) + ' ALTER COLUMN ' + QUOTENAME(@ColumnName) + ' NVARCHAR(' + @MaxLength + ')';
    print @sql;
    EXEC sp_executesql @sql;

    FETCH NEXT FROM column_cursor INTO @TableName, @ColumnName, @MaxLength;
END

CLOSE column_cursor;
DEALLOCATE column_cursor;

-- Declare variables for the next stage
DECLARE @table_name NVARCHAR(255);
DECLARE @column_name NVARCHAR(255);
DECLARE @referencing_table_name NVARCHAR(255);
DECLARE @referenced_table_name NVARCHAR(255);
DECLARE @referencing_column_name NVARCHAR(255);
DECLARE @foreign_key_name NVARCHAR(255);
--DECLARE @sql NVarchar(max);
DECLARE @sqlRunAfterPrimaryKeyRebuilt NVarchar(max);


--Tracked changes made it onto some of our tables that need removing from a prior update, so delete them
DECLARE stopChangeTracking CURSOR FOR
SELECT 
    --schema_name(schema_id) AS schema_name,
    object_name(object_id) AS table_name
FROM 
    sys.change_tracking_tables
where object_name(object_id) like 'BTS_%'

OPEN stopChangeTracking;

-- Loop through UI tables
FETCH NEXT FROM stopChangeTracking INTO @table_name
BEGIN
    set @sql = 'ALTER TABLE ' + @table_name + ' DISABLE CHANGE_TRACKING;';
    PRINT @sql
    EXEC sp_executesql @sql
END

CLOSE stopChangeTracking
DEALLOCATE stopChangeTracking


-- Declare cursor for UniqueIdentifier (UI) tables
DECLARE ui_tables_cursor CURSOR FOR
SELECT 
    t.name AS TableName, 
    c.name AS ColumnName
FROM sys.tables t
INNER JOIN sys.indexes i ON t.object_id = i.object_id AND i.is_primary_key = 1
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
INNER JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = ic.object_id
WHERE 
    type_name(c.user_type_id) = 'uniqueidentifier'
order by t.name


-- Open cursor for UI tables
OPEN ui_tables_cursor;


-- Loop through UI tables
FETCH NEXT FROM ui_tables_cursor INTO @table_name, @column_name;
WHILE @@FETCH_STATUS = 0
BEGIN
    print ''
    print ''
    print '-- ' + @table_name + '.' + @column_name
    print ''
    print '-- New key column'

    -- reset this, as this will hold all the FK's to rebuild once the PK has been realigned
    set @sqlRunAfterPrimaryKeyRebuilt = '';

    -- Add new INT column and populate
    set @sql = 'ALTER TABLE ' + @table_name + ' ADD new_column INT';
    PRINT @sql
    EXEC sp_executesql @sql
    
    set @sql = '
                UPDATE t 
                SET new_column = r.rowN
                from ' + @table_name + ' t
                join 
                    (
                        select 
                            ' + @column_name + ',
                            ROW_NUMBER() OVER (ORDER BY ' + @column_name + ') rowN
                        from ' + @table_name + '
                    ) r on t.' + @column_name + ' = r.' + @column_name + ''
    PRINT @sql
    EXEC sp_executesql @sql        

    -- Declare cursor for referencing tables
    DECLARE referencing_tables_cursor CURSOR FOR
    SELECT OBJECT_NAME(fk.parent_object_id) AS referencing_table_name,
       fk.name AS foreign_key_name,
       OBJECT_NAME(fk.referenced_object_id) AS referenced_table_name,
       (SELECT name 
        FROM sys.columns 
        WHERE object_id = fk.parent_object_id 
        AND column_id = fkc.parent_column_id) AS referencing_column_name
    FROM sys.foreign_keys AS fk
    INNER JOIN sys.foreign_key_columns AS fkc ON fk.object_id = fkc.constraint_object_id
    WHERE OBJECT_NAME(fk.referenced_object_id) = @table_name;

    -- Open cursor for referencing tables
    OPEN referencing_tables_cursor;

    -- Loop through referencing tables
    FETCH NEXT FROM referencing_tables_cursor INTO @referencing_table_name, @foreign_key_name, @referenced_table_name, @referencing_column_name;
    WHILE @@FETCH_STATUS = 0
    BEGIN

        -- Prepare the new column and inherit the nullable property
        DECLARE @IsNullable BIT
        DECLARE @NewColumnName varchar(255)
        set @NewColumnName = @referencing_column_name + '_newKey'
        
        print ''
        print '-- Reference Updates ' + @referencing_table_name

        SELECT @IsNullable = case when c.is_nullable = 'No' then 0 else 1 end
        FROM information_schema.columns c
        WHERE c.table_name = @referencing_table_name
          AND c.column_name = @referencing_column_name;

        SET @sql = 'ALTER TABLE ' + QUOTENAME(@referencing_table_name) + ' ADD ' + QUOTENAME(@NewColumnName) + ' INT NULL;'
        print @sql
        EXEC sp_executesql @sql

        -- Remove all constraint checks
        SET @sql = 'ALTER TABLE ' + QUOTENAME(@referencing_table_name) + ' NOCHECK CONSTRAINT ALL;';
        PRINT @sql
        EXEC sp_executesql @sql

        -- Update new INT FK column with values from old_foreign_key
        SET @sql = 'UPDATE ' + @referencing_table_name + '
                    SET ' + @NewColumnName + ' = (SELECT new_column FROM ' + @table_name + ' WHERE ' + @table_name + '.' + @column_name + ' = ' + @referencing_table_name + '.' + @referencing_column_name + ')';
        PRINT @sql
        EXEC sp_executesql @sql

        -- Drop old FOREIGN KEY constraint
        SET @sql = 'ALTER TABLE ' + @referencing_table_name + ' DROP CONSTRAINT ' + @foreign_key_name;
        PRINT @sql
        EXEC sp_executesql @sql

        -- Now constraints are dropped so delete the old column
        SET @sql = 'ALTER TABLE ' + QUOTENAME(@referencing_table_name) + ' DROP COLUMN ' + @referencing_column_name;
        PRINT @sql
        EXEC sp_executesql @sql

        -- Rename new INT FK column to old_foreign_key and add FOREIGN KEY constraint
        SET @sql = 'EXEC sp_rename ''' + @referencing_table_name + '.' + @NewColumnName + ''', ''' + @referencing_column_name + ''', ''COLUMN''';
        PRINT @sql
        EXEC sp_executesql @sql

        if (@IsNullable = 0)
        BEGIN
            SET @sql = 'ALTER TABLE ' + @referencing_table_name + ' ALTER COLUMN ' + @referencing_column_name + ' INT NOT NULL;';
            print @sql
        EXEC sp_executesql @sql
        END

        -- Prepare a list of all FK constraints for when the new PK is built
        SET @sqlRunAfterPrimaryKeyRebuilt = @sqlRunAfterPrimaryKeyRebuilt + '
ALTER TABLE ' + @referencing_table_name + ' ADD CONSTRAINT ' + @foreign_key_name + ' FOREIGN KEY (' + @referencing_column_name + ') REFERENCES ' + QUOTENAME(@table_name) + '(' + @column_name + ');';

        -- Fetch next referencing table
        FETCH NEXT FROM referencing_tables_cursor INTO @referencing_table_name, @foreign_key_name, @referenced_table_name, @referencing_column_name;
    END

    -- Close referencing tables cursor
    CLOSE referencing_tables_cursor;
    DEALLOCATE referencing_tables_cursor;
    
    print ''
    print '--Primary key updates'

    DECLARE @PrimaryKeyName nvarchar(255)
    
    ---- copy the primary key name to use later when reconstructing the new ID
    SELECT @PrimaryKeyName = TC.CONSTRAINT_NAME
    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
        ON TC.CONSTRAINT_NAME = KCU.CONSTRAINT_NAME
    WHERE TC.TABLE_SCHEMA = OBJECT_SCHEMA_NAME(OBJECT_ID(@table_name))
        AND TC.TABLE_NAME = @table_name
        AND TC.CONSTRAINT_TYPE = 'PRIMARY KEY'
        AND KCU.COLUMN_NAME = @column_name

    SET @sql = 'ALTER TABLE ' + QUOTENAME(@table_name) + ' DROP CONSTRAINT ' +
               (SELECT QUOTENAME(CONSTRAINT_NAME)
                FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
                WHERE TABLE_SCHEMA = OBJECT_SCHEMA_NAME(OBJECT_ID(@table_name))
                  AND TABLE_NAME = @table_name
                  AND CONSTRAINT_TYPE = 'PRIMARY KEY'
               );
    PRINT @sql;
    EXEC sp_executesql @sql;    

    print '-- dropped the constraint'

    WAITFOR DELAY '00:00:01'; -- Wait for 1 second (adjust the delay as needed) in case the schema needs to refresh, seems odd to add this but it helped

    declare @NewIDConstraintDetector NVARCHAR(255)
    SELECT @NewIDConstraintDetector = name
    FROM sys.default_constraints
    WHERE parent_object_id = OBJECT_ID(@table_name)
    AND type_desc = 'DEFAULT_CONSTRAINT'
    AND definition LIKE '%NEWID()%';

    if (@NewIDConstraintDetector is not null)
    BEGIN
        BEGIN TRY
            SET @sql = 'ALTER TABLE [' + @table_name + '] DROP CONSTRAINT [' + @NewIDConstraintDetector + ']';
            PRINT @sql;
            EXEC sp_executesql @sql;
        END TRY
        BEGIN CATCH
            PRINT 'An error occurred: ' + ERROR_MESSAGE();
        END CATCH
    END


    -- Remove primary key column and set new INT column as primary key
    SET @sql = 'ALTER TABLE ' + @table_name + ' DROP COLUMN ' + QUOTENAME(@column_name);
    PRINT @sql
    EXEC sp_executesql @sql

    SET @sql = 'EXEC sp_rename ''' + @table_name + '.new_column'', ' + QUOTENAME(@column_name) + ', ''COLUMN''';
    PRINT @sql
    EXEC sp_executesql @sql

    -- we need to create an identity column
    -- this can only be done by cloning the table to a temp table ensuring the ID is set as an INT IDENEITY
    -- then copy the data in with identity insertion
    -- remove the indetity insertion
    -- delete the original table
    -- rename temp table to the original table, so here goes :)

    SET @sql = 'CREATE TABLE Temp' + @table_name + ' (';

    DECLARE @ColumnList NVARCHAR(MAX);

    -- Concatenate column names into a comma-delimited string
    SELECT @ColumnList = STRING_AGG(QUOTENAME(name), ', ')
    FROM sys.columns
    WHERE object_id = OBJECT_ID(QUOTENAME(@table_name)); -- Replace 'YourTableName' with the actual name of your table

    DECLARE @ColumnDefinitions NVARCHAR(MAX) = '';

    -- Get column definitions
    SELECT @ColumnDefinitions += 
        c.name + ' ' + 
        CASE 
            WHEN c.name = @column_name THEN 'INT IDENTITY(1,1)'
            WHEN c.system_type_id = 56 THEN 'INT'
            WHEN c.system_type_id = 231 AND c.max_length = -1 THEN 'NVARCHAR(MAX)' -- For NVARCHAR, use the original max_length
            WHEN c.system_type_id = 231 THEN 'NVARCHAR(' + CAST(c.max_length/2 AS VARCHAR(10)) + ')'
            WHEN c.system_type_id = 167 AND c.max_length = -1 THEN 'NVARCHAR(MAX)' -- For VARCHAR(MAX)
            WHEN c.system_type_id = 167 THEN 'NVARCHAR(' + CAST(c.max_length/2 AS VARCHAR(10)) + ')' -- For VARCHAR with defined size
            WHEN c.system_type_id IN (106, 108, 122) THEN 'NUMERIC(' + CAST(c.precision AS VARCHAR(10)) + ',' + CAST(c.scale AS VARCHAR(10)) + ')'
            WHEN c.system_type_id IN (175, 239) THEN 'NVARCHAR(MAX)'
            WHEN c.system_type_id = 104 THEN 'BIT'
            WHEN c.system_type_id = 61 THEN 'DATETIME2'
            WHEN c.system_type_id = 36 THEN 'UNIQUEIDENTIFIER'
            ELSE 'NVARCHAR(MAX)' -- Default to NVARCHAR(MAX) for unknown types
        END + ', '
    FROM sys.columns c
    INNER JOIN sys.tables t ON c.object_id = t.object_id
    WHERE t.name = @table_name;

    --Use AIMS_Version
    --SELECT system_type_id, max_length
    --FROM sys.columns
    --WHERE object_id = OBJECT_ID('BTS_BTSOnlineUsers') -- Replace 'YourTableName' with the name of your table
    --  AND name = 'Username'; 

    --  167

    --  167
    --  LastViewMode

    --  231

    -- Remove trailing comma and space
    SET @ColumnDefinitions = LEFT(@ColumnDefinitions, LEN(@ColumnDefinitions) - 1);
    SET @sql = @sql + @ColumnDefinitions + ');'
    PRINT @sql
    EXEC sp_executesql @sql

    --restore FKs to the new table
    DECLARE @FKConstraints NVARCHAR(MAX) = '';

    -- Retrieve foreign key information
    SELECT @FKConstraints += 
        'ALTER TABLE Temp' + @table_name + 
        ' ADD CONSTRAINT ' + QUOTENAME('removeThisText_' + fk.name) + 
        ' FOREIGN KEY (' + STUFF((
            SELECT ',' + QUOTENAME(c.name)
            FROM sys.columns c
            INNER JOIN sys.foreign_key_columns fkc ON c.object_id = fkc.parent_object_id AND c.column_id = fkc.parent_column_id
            WHERE fkc.constraint_object_id = fk.object_id
            FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') +
        ') REFERENCES ' + QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) +
        ' (' + STUFF((
            SELECT ',' + QUOTENAME(c.name)
            FROM sys.columns c
            INNER JOIN sys.foreign_key_columns fkc ON c.object_id = fkc.referenced_object_id AND c.column_id = fkc.referenced_column_id
            WHERE fkc.constraint_object_id = fk.object_id
            FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') +
        ')' +
        CASE 
            WHEN fk.delete_referential_action = 1 THEN ' ON DELETE CASCADE'
            WHEN fk.delete_referential_action = 2 THEN ' ON DELETE SET NULL'
            ELSE ''
        END +
        CASE 
            WHEN fk.update_referential_action = 1 THEN ' ON UPDATE CASCADE'
            WHEN fk.update_referential_action = 2 THEN ' ON UPDATE SET NULL'
            ELSE ''
        END +
        ';' + CHAR(13) + CHAR(10)
    FROM sys.foreign_keys fk
    INNER JOIN sys.tables t ON fk.referenced_object_id = t.object_id
    WHERE fk.parent_object_id = OBJECT_ID(@table_name);

    -- Execute the ALTER TABLE statements to recreate foreign key constraints on the new table
    EXEC sp_executesql @FKConstraints;


    SET @sql = 'SET IDENTITY_INSERT Temp' + @table_name + ' ON
                INSERT INTO Temp' + @table_name + ' (' + @ColumnList + ') SELECT ' + @ColumnList + ' FROM ' + @table_name + '
                SET IDENTITY_INSERT Temp' + @table_name + ' OFF'
    PRINT @sql
    EXEC sp_executesql @sql

    SET @sql = 'DROP TABLE dbo.' + @table_name 
    PRINT @sql
    EXEC sp_executesql @sql

    SET @sql = 'EXEC sp_rename ''Temp' + @table_name + ''', ''' + @table_name + ''', ''OBJECT'';'
    PRINT @sql
    EXEC sp_executesql @sql
    
    
    -- Return the Primary key with the same naming
    SET @sql = 'ALTER TABLE ' + @table_name + ' ADD CONSTRAINT ' + @PrimaryKeyName + ' PRIMARY KEY (' + @column_name +')';
    PRINT @sql
    EXEC sp_executesql @sql

    print ''
    print '--All reference table FKs enforced'
    print @sqlRunAfterPrimaryKeyRebuilt
    EXEC sp_executesql @sqlRunAfterPrimaryKeyRebuilt

    -- Fetch next UI table
    FETCH NEXT FROM ui_tables_cursor INTO @table_name, @column_name;
END

-- Close UI tables cursor
CLOSE ui_tables_cursor
DEALLOCATE ui_tables_cursor

-- Update all the FK names

DECLARE @OldConstraintName NVARCHAR(MAX);
DECLARE @NewConstraintName NVARCHAR(MAX);

DECLARE FKCursor CURSOR FOR
SELECT name
FROM sys.foreign_keys
WHERE name LIKE 'removeThisText_%';

OPEN FKCursor;

FETCH NEXT FROM FKCursor INTO @OldConstraintName;

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @NewConstraintName = REPLACE(@OldConstraintName, 'removeThisText_', '');

    -- Construct and execute the ALTER TABLE statement to rename the foreign key
    SET @sql = 'EXEC sp_rename ''' + @OldConstraintName + ''', ''' + @NewConstraintName + ''';';
    print @sql
    EXEC sp_executesql @sql;

    FETCH NEXT FROM FKCursor INTO @OldConstraintName;
END

CLOSE FKCursor;
DEALLOCATE FKCursor;
© www.soinside.com 2019 - 2024. All rights reserved.