我设计了一个数据库,所有表的主键类型都是
uniqueidentifier
。它有 50 个表和现有数据。然后,我知道这是一个坏主意。我想从 int
更改为 uniqueidentifier
pk 类型。
我该怎么办?如何移动外键?
刚刚完成了一个脚本,在几个表上进行了测试并且工作正常,在生产环境中执行之前请自行测试。
该脚本执行以下操作。
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
我们陷入了同样的困境。我们需要集成一个新的供应商包,我们所有的 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;