ป้ายกำกับ: Table

SQL Server: batch migrate sync tableSQL Server: batch migrate sync table

เป็นเพราะว่า sa ใน team มีคนเข้ามาแก้ table หลายคน แถมบาง database ก็ไม่ได้แก้ จะมาไล่แก้ทุก ๆ ที่มันก็ไม่ไหว เขียน sync script ออกมาเพื่อที่จะให้มันเปลี่ยน name, type, order, description, Foreign Keys, Indexes ได้จากจุดเดียวไปเลย
batch_migrate_sync_table.sql

-- USE DB_DEV;
-- USE DB_QA;
-- USE DB_PROD; SET NOCOUNT ON; BEGIN TRY
BEGIN TRANSACTION -- ============================================= -- 1. CONFIGURATION -- ============================================= DECLARE @SchemaName NVARCHAR (128) = '' DECLARE @TableName NVARCHAR (128) = 'wp_posts' -- Table Description DECLARE @TableDesc NVARCHAR (4000) = N'สำหรับเก็บ post'; -- Check for NULL or Empty Schema -> Default to 'dbo' IF @SchemaName IS NULL OR LTRIM (RTRIM (@SchemaName)) = '' SET @SchemaName = 'dbo'; -- Standard Variables DECLARE @TempTableName NVARCHAR (128) = @TableName + '_dup' DECLARE @OldTableName NVARCHAR (128) = @TableName + '_old' DECLARE @TargetObjID INT = OBJECT_ID (QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName)) -- Dynamic SQL Variables DECLARE @SqlDropFK NVARCHAR (MAX) = '', @SqlCreateFK NVARCHAR (MAX) = '', @SqlCreatePK_Indexes NVARCHAR (MAX) = '' DECLARE @CreateTableSQL NVARCHAR (MAX) , @CopySQL NVARCHAR (MAX) , @DropOldSQL NVARCHAR (MAX) DECLARE @ColList NVARCHAR (MAX) = '', @SharedColList NVARCHAR (MAX) = '', @Msg NVARCHAR (MAX) DECLARE @IdentityColName NVARCHAR (128) = NULL DECLARE @PreserveIdentity BIT = 0 DECLARE @CurrentFullName NVARCHAR (256) , @TempFullName NVARCHAR (256) -- Cursor Helpers DECLARE @Cur_ColName NVARCHAR (128) , @Cur_DataType NVARCHAR (100) , @Cur_IsNullable CHAR (1) , @Cur_Desc NVARCHAR (4000) DECLARE @Cur_FKID INT, @Cur_FKName NVARCHAR (128) , @Cur_ParentSchema NVARCHAR (128) , @Cur_ParentTable NVARCHAR (128) DECLARE @Helper_FKCols NVARCHAR (MAX) , @Helper_RefCols NVARCHAR (MAX) DECLARE @Cur_IdxID INT, @Cur_IdxName NVARCHAR (128) , @Cur_IsPK BIT, @Cur_IsUnique BIT, @Cur_IdxType TINYINT DECLARE @Helper_IdxCols NVARCHAR (MAX) , @Helper_IncCols NVARCHAR (MAX) -- ============================================= -- 2. DEFINE NEW STRUCTURE -- ============================================= DECLARE @ColumnDefs TABLE (ID INT IDENTITY (1,1) , ColumnName NVARCHAR (128) , DataType NVARCHAR (100) , IsNullable CHAR (1) , -- 'Y' = NULL, 'N' = NOT NULL Description NVARCHAR (4000)) ; -- Insert Columns INSERT INTO @ColumnDefs (ColumnName, DataType, IsNullable, Description) VALUES ('ID', 'BIGINT IDENTITY (1,1) ', 'N', 'Primary Key') , ('post_author', 'BIGINT', 'N', 'Author ID') , ('post_date', 'DATETIME2 (0) ', 'N', 'Post Date') , ('post_date_gmt', 'DATETIME2 (0) ', 'N', 'Post Date GMT') , ('post_content', 'NVARCHAR (MAX) ', 'N', 'Post Content') , ('post_title', 'NVARCHAR (MAX) ', 'N', 'Post Title') , ('post_excerpt', 'NVARCHAR (MAX) ', 'N', 'Post Excerpt') , ('post_status', 'NVARCHAR (20) ', 'N', 'Post Status (publish, draft, etc.) ') , ('comment_status', 'NVARCHAR (20) ', 'N', 'Comment Status (open/closed) ') , ('ping_status', 'NVARCHAR (20) ', 'N', 'Ping Status') , ('post_password', 'NVARCHAR (255) ', 'N', 'Post Password') , ('post_name', 'NVARCHAR (200) ', 'N', 'Post Name (Slug) ') , ('to_ping', 'NVARCHAR (MAX) ', 'N', 'URLs to ping') , ('pinged', 'NVARCHAR (MAX) ', 'N', 'Pinged URLs') , ('post_modified', 'DATETIME2 (0) ', 'N', 'Modification Date') , ('post_modified_gmt', 'DATETIME2 (0) ', 'N', 'Modification Date GMT') , ('post_content_filtered', 'NVARCHAR (MAX) ', 'N', 'Filtered Content') , ('post_parent', 'BIGINT', 'N', 'Parent Post ID') , ('guid', 'NVARCHAR (255) ', 'N', 'Global Unique ID') , ('menu_order', 'INT', 'N', 'Menu Order') , ('post_type', 'NVARCHAR (20) ', 'N', 'Post Type (post, page, etc.) ') , ('post_mime_type', 'NVARCHAR (100) ', 'N', 'Mime Type') , ('comment_count', 'BIGINT', 'N', 'Comment Count') ; -- ============================================= -- 2.1 AUTO-DISCOVERY & IDENTITY CHECK -- ============================================= -- Detect Identity Column Name in New Structure SELECT TOP 1 @IdentityColName = ColumnName FROM @ColumnDefs WHERE DataType LIKE '%IDENTITY%'; IF @TargetObjID IS NOT NULL BEGIN PRINT '... Auto-detecting missing columns from existing table ...' INSERT INTO @ColumnDefs (ColumnName, DataType, IsNullable, Description) SELECT c.name, CASE WHEN t.name IN ('varchar', 'char', 'varbinary', 'binary') THEN t.name + ' (' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST (c.max_length AS VARCHAR) END + ') ' WHEN t.name IN ('nvarchar', 'nchar') THEN t.name + ' (' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST (c.max_length / 2 AS VARCHAR) END + ') ' WHEN t.name IN ('decimal', 'numeric') THEN t.name + ' (' + CAST (c.precision AS VARCHAR) + ',' + CAST (c.scale AS VARCHAR) + ') ' ELSE t.name END, CASE WHEN c.is_nullable = 1 THEN 'Y' ELSE 'N' END, ISNULL (CAST (ep.value AS NVARCHAR (4000)) , '') FROM sys.columns c JOIN sys.types t ON c.user_type_id = t.user_type_id LEFT JOIN sys.extended_properties ep ON ep.major_id = c.object_id AND ep.minor_id = c.column_id AND ep.name = 'MS_Description' WHERE c.object_id = @TargetObjID AND c.is_computed = 0 AND c.name NOT IN (SELECT ColumnName FROM @ColumnDefs) ORDER BY c.column_id; PRINT ' -> Auto-discovery complete.' END -- ============================================= -- 3. BACKUP RELATIONS -- ============================================= IF @TargetObjID IS NOT NULL BEGIN PRINT '... Backing up Relations/Indexes ...' -- FKs DECLARE FKCursor CURSOR LOCAL FAST_FORWARD FOR SELECT fk.object_id, fk.name, SCHEMA_NAME (fk.schema_id) , OBJECT_NAME (fk.parent_object_id) FROM sys.foreign_keys fk WHERE fk.referenced_object_id = @TargetObjID; OPEN FKCursor; FETCH NEXT FROM FKCursor INTO @Cur_FKID, @Cur_FKName, @Cur_ParentSchema, @Cur_ParentTable; WHILE @@FETCH_STATUS = 0 BEGIN SELECT @Helper_FKCols = STUFF ( (SELECT ',' + QUOTENAME (c.name) FROM sys.foreign_key_columns fkc JOIN sys.columns c ON fkc.parent_object_id = c.object_id AND fkc.parent_column_id = c.column_id WHERE fkc.constraint_object_id = @Cur_FKID ORDER BY fkc.constraint_column_id FOR XML PATH ('')) , 1, 1, '') ; SELECT @Helper_RefCols = STUFF ( (SELECT ',' + QUOTENAME (c.name) FROM sys.foreign_key_columns fkc JOIN sys.columns c ON fkc.referenced_object_id = c.object_id AND fkc.referenced_column_id = c.column_id WHERE fkc.constraint_object_id = @Cur_FKID ORDER BY fkc.constraint_column_id FOR XML PATH ('')) , 1, 1, '') ; SET @SqlDropFK += 'ALTER TABLE ' + QUOTENAME (@Cur_ParentSchema) + '.' + QUOTENAME (@Cur_ParentTable) + ' DROP CONSTRAINT ' + QUOTENAME (@Cur_FKName) + ';' + CHAR (13) ; SET @SqlCreateFK += 'ALTER TABLE ' + QUOTENAME (@Cur_ParentSchema) + '.' + QUOTENAME (@Cur_ParentTable) + ' WITH NOCHECK ADD CONSTRAINT ' + QUOTENAME (@Cur_FKName) + ' FOREIGN KEY (' + @Helper_FKCols + ') REFERENCES ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName) + ' (' + @Helper_RefCols + ') ;' + CHAR (13) ; FETCH NEXT FROM FKCursor INTO @Cur_FKID, @Cur_FKName, @Cur_ParentSchema, @Cur_ParentTable; END CLOSE FKCursor; DEALLOCATE FKCursor; -- Indexes DECLARE IdxCursor CURSOR LOCAL FAST_FORWARD FOR SELECT index_id, name, is_primary_key, is_unique, type FROM sys.indexes WHERE object_id = @TargetObjID AND type IN (1, 2) ORDER BY is_primary_key DESC; OPEN IdxCursor; FETCH NEXT FROM IdxCursor INTO @Cur_IdxID, @Cur_IdxName, @Cur_IsPK, @Cur_IsUnique, @Cur_IdxType; WHILE @@FETCH_STATUS = 0 BEGIN SELECT @Helper_IdxCols = STUFF ( (SELECT ',' + QUOTENAME (c.name) + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END FROM sys.index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic.object_id = @TargetObjID AND ic.index_id = @Cur_IdxID AND ic.is_included_column = 0 ORDER BY ic.key_ordinal FOR XML PATH ('')) , 1, 1, '') ; SELECT @Helper_IncCols = STUFF ( (SELECT ',' + QUOTENAME (c.name) FROM sys.index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic.object_id = @TargetObjID AND ic.index_id = @Cur_IdxID AND ic.is_included_column = 1 ORDER BY ic.column_id FOR XML PATH ('')) , 1, 1, '') ; IF @Cur_IsPK = 1 SET @SqlCreatePK_Indexes += 'ALTER TABLE ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName) + ' ADD CONSTRAINT ' + QUOTENAME (@Cur_IdxName) + ' PRIMARY KEY ' + CASE WHEN @Cur_IdxType = 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END + ' (' + @Helper_IdxCols + ') ;' + CHAR (13) ; ELSE SET @SqlCreatePK_Indexes += 'CREATE ' + CASE WHEN @Cur_IsUnique = 1 THEN 'UNIQUE ' ELSE '' END + 'NONCLUSTERED INDEX ' + QUOTENAME (@Cur_IdxName) + ' ON ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName) + ' (' + @Helper_IdxCols + ') ' + CASE WHEN LEN (@Helper_IncCols) > 0 THEN ' INCLUDE (' + @Helper_IncCols + ') ' ELSE '' END + ';' + CHAR (13) ; FETCH NEXT FROM IdxCursor INTO @Cur_IdxID, @Cur_IdxName, @Cur_IsPK, @Cur_IsUnique, @Cur_IdxType; END CLOSE IdxCursor; DEALLOCATE IdxCursor; END -- ============================================= -- 4. BUILD SCRIPTS & CHECK SHARED IDENTITY -- ============================================= DECLARE ScriptCursor CURSOR LOCAL FAST_FORWARD FOR SELECT ColumnName, DataType, IsNullable FROM @ColumnDefs ORDER BY ID; OPEN ScriptCursor; FETCH NEXT FROM ScriptCursor INTO @Cur_ColName, @Cur_DataType, @Cur_IsNullable; WHILE @@FETCH_STATUS = 0 BEGIN -- Build Create List IF LEN (@ColList) > 0 SET @ColList += ', ' + CHAR (13) + CHAR (10) + ' '; SET @ColList += QUOTENAME (@Cur_ColName) + ' ' + @Cur_DataType + CASE WHEN @Cur_IsNullable = 'Y' THEN ' NULL' ELSE ' NOT NULL' END; -- Build Shared Column List IF @TargetObjID IS NOT NULL AND EXISTS (SELECT 1 FROM sys.columns WHERE object_id = @TargetObjID AND name = @Cur_ColName) BEGIN IF LEN (@SharedColList) > 0 SET @SharedColList += ', '; SET @SharedColList += QUOTENAME (@Cur_ColName) ; -- [KEY FIX] If this shared column is the Identity Column, mark flag to Preserve Identity IF @IdentityColName IS NOT NULL AND @Cur_ColName = @IdentityColName BEGIN SET @PreserveIdentity = 1; END END FETCH NEXT FROM ScriptCursor INTO @Cur_ColName, @Cur_DataType, @Cur_IsNullable; END CLOSE ScriptCursor; DEALLOCATE ScriptCursor; -- ============================================= -- 5. EXECUTION -- ============================================= -- 5.1 Create Temp Table SET @CreateTableSQL = 'CREATE TABLE ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TempTableName) + ' (' + @ColList + ') ;'; PRINT 'Creating Temp Table...' EXEC sp_executesql @CreateTableSQL; -- 5.2 Copy Data IF @TargetObjID IS NOT NULL AND LEN (@SharedColList) > 0 BEGIN IF LEN (@SqlDropFK) > 0 EXEC sp_executesql @SqlDropFK; PRINT 'Copying data...' SET @CopySQL = ''; -- Only set IDENTITY_INSERT ON if we are actually copying the Identity Column IF @PreserveIdentity = 1 BEGIN SET @CopySQL += 'SET IDENTITY_INSERT ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TempTableName) + ' ON; '; END SET @CopySQL += 'INSERT INTO ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TempTableName) + ' (' + @SharedColList + ') ' + 'SELECT ' + @SharedColList + ' FROM ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName) + '; '; IF @PreserveIdentity = 1 BEGIN SET @CopySQL += 'SET IDENTITY_INSERT ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TempTableName) + ' OFF; '; END EXEC sp_executesql @CopySQL; END -- 5.3 Column Descriptions DECLARE DescCursor CURSOR LOCAL FAST_FORWARD FOR SELECT ColumnName, Description FROM @ColumnDefs WHERE Description IS NOT NULL AND Description <> ''; OPEN DescCursor; FETCH NEXT FROM DescCursor INTO @Cur_ColName, @Cur_Desc; WHILE @@FETCH_STATUS = 0 BEGIN EXEC sp_addextendedproperty @name = N'MS_Description', @value = @Cur_Desc, @level0type = N'SCHEMA', @level0name = @SchemaName, @level1type = N'TABLE', @level1name = @TempTableName, @level2type = N'COLUMN', @level2name = @Cur_ColName; FETCH NEXT FROM DescCursor INTO @Cur_ColName, @Cur_Desc; END CLOSE DescCursor; DEALLOCATE DescCursor; -- 5.4 Table Description PRINT 'Adding Table Description...' EXEC sp_addextendedproperty @name = N'MS_Description', @value = @TableDesc, @level0type = N'SCHEMA', @level0name = @SchemaName, @level1type = N'TABLE', @level1name = @TempTableName; -- 5.5 Swap Tables IF @TargetObjID IS NOT NULL BEGIN PRINT 'Swapping tables...' SET @CurrentFullName = QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName) ; SET @TempFullName = QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TempTableName) ; EXEC sp_rename @CurrentFullName, @OldTableName; EXEC sp_rename @TempFullName, @TableName; SET @DropOldSQL = 'DROP TABLE ' + QUOTENAME (@SchemaName) + '.' + QUOTENAME (@OldTableName) + ';'; EXEC sp_executesql @DropOldSQL; IF LEN (@SqlCreatePK_Indexes) > 0 EXEC sp_executesql @SqlCreatePK_Indexes; IF LEN (@SqlCreateFK) > 0 EXEC sp_executesql @SqlCreateFK; END ELSE BEGIN SET @TempFullName = QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TempTableName) ; EXEC sp_rename @TempFullName, @TableName; END COMMIT TRANSACTION
PRINT 'Migration Completed Successfully.' END TRY
BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; SET @Msg = 'ERROR: ' + ERROR_MESSAGE () + ' (Line: ' + CAST (ERROR_LINE () AS NVARCHAR (10)) + ') '; PRINT @Msg;
END CATCH;

พอเปลี่ยน table ก็เปลี่ยนชื่อ table และ column ที่มี script จะไปไล่แก้ column ตามลำดับเอง ส่วน column ที่ไม่อยู่ในลิสต์ก็จะโดนไว้ล่างสุด ถึงจะไม่ได้แก้ให้ตรง 100 % แต่ก็ดีกว่ามาเขียน SQL หรือใช้ UI ไล่แก้ที่ละจุดเยอะ