PlusMagi's Blog By Pitt Phunsanit SQL Server SQL Server: ย้าย column ในตาราง

SQL Server: ย้าย column ในตาราง

ข้อเสียหลัก ๆ ของ SQL Server คือ การแก้ตารางทีหลังโดย SQL command ได้ยากว่าชาวบ้านเค้า อย่าง MSSQL: ย้าย / เพิ่ม column ก่อนหลัง column วิธีที่ใช้ง่ายที่สุดคือ SQL Server Management Studio แต่ถ้าไม่ได้ติดตั้งไว้ การใช้ SQL query ตรง ๆ มันจะไม่มีเหมือนตัวอื่น ทำให้เขียน script ที่ไม่สั้นเท่าไหร่ เพื่อเปลี่ยนลำดับ column ใหม่
/MSSQL/alter_table_set_column_after_column.sql

/**************************************************************************************************
	Script to Reorder Columns (Final Fixed: PK Name Collision Handled) Fixes:
	1. Renames the OLD PK on the backup table first to free up the name.
	2. Handles Identity Check.
	3. Fully compatible syntax.
**************************************************************************************************/

-- USE DB_DEV;
-- USE DB_QA;
-- USE DB_PROD;

-- ==================== Edit only these 3 lines ====================
DECLARE @SchemaName NVARCHAR (128) = N'FINANCE';
DECLARE @TableName NVARCHAR (128) = N'TEMP_AR_ISSUE_DNCN';

-- Enter the column names to be ordered first
DECLARE @DesiredColumnOrder NVARCHAR (MAX) = N'
PP_ID,
PP_CREATE_BY,
PP_CREATE_DT,
PP_MODIFY_BY,
PP_MODIFY_DT,
PP_TIMESTAMP,

PP_TITLE,
PP_CONTENT
';
-- =================================================================

IF @SchemaName IS NULL OR @SchemaName = N'' SET @SchemaName = N'dbo';

SET NOCOUNT ON;
DECLARE @NewTable	 NVARCHAR (258) = QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName + '_New') ;
DECLARE @OldTable	 NVARCHAR (258) = QUOTENAME (@SchemaName) + '.' + QUOTENAME (@TableName) ;
DECLARE @SQL		 NVARCHAR (MAX) = N'';
DECLARE @DropFK		 NVARCHAR (MAX) = N'';
DECLARE @CreateFK	 NVARCHAR (MAX) = N'';
DECLARE @ColList	 NVARCHAR (MAX) = N'';
DECLARE @PKCols		 NVARCHAR (MAX) = N'';
DECLARE @HasIdentity BIT = 0;

-- Clean spaces AND Newlines (CR/LF) SET @DesiredColumnOrder = REPLACE (@DesiredColumnOrder, ' ', '') ;
SET @DesiredColumnOrder = REPLACE (@DesiredColumnOrder, CHAR (13) , '') ; -- Remove Carriage Return
SET @DesiredColumnOrder = REPLACE (@DesiredColumnOrder, CHAR (10) , '') ; -- Remove Line Feed

-- 1. Get FK Scripts
SELECT
	@DropFK = @DropFK + N'ALTER TABLE ' + QUOTENAME (OBJECT_SCHEMA_NAME (parent_object_id)) + '.' + QUOTENAME (OBJECT_NAME (parent_object_id)) + N' DROP CONSTRAINT ' + QUOTENAME (fk.name) + N';' + CHAR (10) ,
	@CreateFK = @CreateFK + N'ALTER TABLE ' + QUOTENAME (OBJECT_SCHEMA_NAME (parent_object_id)) + '.' + QUOTENAME (OBJECT_NAME (parent_object_id)) + N' WITH CHECK ADD CONSTRAINT ' + QUOTENAME (fk.name) + N' FOREIGN KEY ('
				+ STUFF ( (SELECT ',' + QUOTENAME (COL_NAME (fc2.parent_object_id, fc2.parent_column_id)) FROM sys.foreign_key_columns fc2
							WHERE fc2.constraint_object_id = fk.object_id
							ORDER BY fc2.constraint_column_id
							FOR XML PATH ('')) ,1,1,'') + N') REFERENCES '
				+ QUOTENAME (OBJECT_SCHEMA_NAME (referenced_object_id)) + '.' + QUOTENAME (OBJECT_NAME (referenced_object_id)) + N' ('
				+ STUFF ( (SELECT ',' + QUOTENAME (COL_NAME (fc2.referenced_object_id, fc2.referenced_column_id)) FROM sys.foreign_key_columns fc2
							WHERE fc2.constraint_object_id = fk.object_id
							ORDER BY fc2.constraint_column_id
							FOR XML PATH ('')) ,1,1,'') + N') ;' + CHAR (10) + N'ALTER TABLE ' + QUOTENAME (OBJECT_SCHEMA_NAME (parent_object_id)) + '.' + QUOTENAME (OBJECT_NAME (parent_object_id)) + N' CHECK CONSTRAINT ' + QUOTENAME (fk.name) + N';' + CHAR (10) FROM sys.foreign_keys fk
WHERE referenced_object_id = OBJECT_ID (@OldTable) OR parent_object_id = OBJECT_ID (@OldTable) ;

-- 2. Begin Transaction
BEGIN TRANSACTION;
BEGIN TRY

	-- Drop FKs
	IF LEN (@DropFK) > 0 EXEC sp_executesql @DropFK;

	-- 3. Prepare Columns Data
	DECLARE @ColsTable TABLE (ColName	 NVARCHAR (128) ,
		DataType	NVARCHAR (128) ,
		IsNullable VARCHAR (3) ,
		ColDefault NVARCHAR (MAX) ,
		IsIdentity BIT,
		IsComputed BIT,
		ComputedDef NVARCHAR (MAX) ,
		IsPersisted BIT,
		SortOrder INT) ;

	INSERT INTO @ColsTable
	SELECT
		c.COLUMN_NAME,
		DATA_TYPE =
				CASE
					WHEN c.DATA_TYPE IN ('varchar','char','nvarchar','nchar','varbinary','binary') AND c.CHARACTER_MAXIMUM_LENGTH = -1 THEN c.DATA_TYPE + ' (MAX) '
					WHEN c.DATA_TYPE IN ('varchar','char','nvarchar','nchar','varbinary','binary') THEN c.DATA_TYPE + ' (' + CASE WHEN c.CHARACTER_MAXIMUM_LENGTH = -1 THEN 'MAX' ELSE CAST (c.CHARACTER_MAXIMUM_LENGTH AS VARCHAR (10)) END + ') '
					WHEN c.DATA_TYPE IN ('decimal','numeric') THEN c.DATA_TYPE + ' (' + CAST (c.NUMERIC_PRECISION AS VARCHAR (10)) + ',' + CAST (c.NUMERIC_SCALE AS VARCHAR (10)) + ') '
					WHEN c.DATA_TYPE IN ('float','real') THEN c.DATA_TYPE + CASE WHEN c.NUMERIC_PRECISION IS NULL THEN '' ELSE ' (' + CAST (c.NUMERIC_PRECISION AS VARCHAR (10)) + ') ' END
					WHEN c.DATA_TYPE = 'datetime2' THEN c.DATA_TYPE + ' (' + CAST (c.DATETIME_PRECISION AS VARCHAR (10)) + ') '
					ELSE c.DATA_TYPE
				END,
		IS_NULLABLE = c.IS_NULLABLE,
		COLUMN_DEFAULT = c.COLUMN_DEFAULT,
		IS_IDENTITY = CASE WHEN ic.column_id IS NOT NULL THEN 1 ELSE 0 END,
		IS_COMPUTED = CASE WHEN cc.definition IS NOT NULL THEN 1 ELSE 0 END,
		COMPUTED_DEF = cc.definition,
		IS_PERSISTED = cc.is_persisted,
		SortOrder = CASE
							WHEN CHARINDEX (',' + c.COLUMN_NAME + ',', ',' + @DesiredColumnOrder + ',') > 0
							THEN CHARINDEX (',' + c.COLUMN_NAME + ',', ',' + @DesiredColumnOrder + ',') ELSE 100000 + c.ORDINAL_POSITION
					END
	FROM INFORMATION_SCHEMA.COLUMNS c
	LEFT JOIN sys.identity_columns ic ON OBJECT_ID (@OldTable) = ic.object_id AND c.COLUMN_NAME = ic.name
	LEFT JOIN sys.computed_columns cc ON OBJECT_ID (@OldTable) = cc.object_id AND c.COLUMN_NAME = cc.name
	WHERE c.TABLE_SCHEMA = @SchemaName AND c.TABLE_NAME = @TableName;

	-- Check Identity
	IF EXISTS (SELECT 1 FROM @ColsTable WHERE IsIdentity = 1) SET @HasIdentity = 1;

	-- 4. Build CREATE TABLE Script
	SET @SQL = N'CREATE TABLE ' + @NewTable + ' (' + CHAR (10) ;

	DECLARE @ColsDef NVARCHAR (MAX) ;
	SELECT @ColsDef = STUFF ( (SELECT ',' + CHAR (10) + ' [' + ColName + '] ' + DataType +
					CASE WHEN IsIdentity = 1 THEN ' IDENTITY (1,1) ' ELSE '' END +
					CASE WHEN IsComputed = 1 THEN ' AS ' + ComputedDef + CASE WHEN IsPersisted = 1 THEN ' PERSISTED' ELSE ' ' END ELSE ' ' END +
					' ' + CASE WHEN IsNullable = 'YES' OR IsComputed = 1 THEN 'NULL' ELSE 'NOT NULL' END +
					-- Add '_New' to constraint name to avoid duplicate object name error
					ISNULL (' CONSTRAINT [DF_' + @TableName + '_New_' + ColName + '] DEFAULT ' + ColDefault, ' ') FROM @ColsTable
		ORDER BY SortOrder
		FOR XML PATH ('') , TYPE) .value ('.', 'NVARCHAR (MAX) ') , 1, 1, '') ;

	SET @SQL = @SQL + @ColsDef;

	-- Add PK
	IF EXISTS (SELECT 1 FROM sys.key_constraints WHERE type = 'PK' AND parent_object_id = OBJECT_ID (@OldTable)) BEGIN
		SELECT @PKCols = STUFF ( (SELECT ', [' + COL_NAME (ic.object_id, ic.column_id) + '] ' + CASE WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END
				FROM sys.key_constraints kc
				JOIN sys.index_columns ic ON kc.parent_object_id = ic.object_id AND kc.unique_index_id = ic.index_id
				WHERE kc.type = 'PK' AND kc.parent_object_id = OBJECT_ID (@OldTable) ORDER BY ic.key_ordinal
				FOR XML PATH ('')) , 1, 2, '') ;

		SET @SQL = @SQL + CHAR (10) + ', CONSTRAINT [PK_' + @TableName + '_Temp] PRIMARY KEY CLUSTERED (' + @PKCols + ') ';
	END

	SET @SQL = @SQL + CHAR (10) + ') ;';

	EXEC sp_executesql @SQL;

	-- 5. Copy Data
	SELECT @ColList = STUFF ( (SELECT ', [' + ColName + ']'
		FROM @ColsTable
		WHERE IsComputed = 0
		ORDER BY SortOrder
		FOR XML PATH ('')) , 1, 2, '') ;

	SET @SQL = N'';
	IF @HasIdentity = 1
		SET @SQL = @SQL + N'SET IDENTITY_INSERT ' + @NewTable + ' ON; ' + CHAR (10) ;

	SET @SQL = @SQL + N'INSERT INTO ' + @NewTable + ' (' + @ColList + ') ' + CHAR (10) +
					 N'SELECT ' + @ColList + ' FROM ' + @OldTable + '; ' + CHAR (10) ;

	IF @HasIdentity = 1
		SET @SQL = @SQL + N'SET IDENTITY_INSERT ' + @NewTable + ' OFF;';

	EXEC sp_executesql @SQL;
	PRINT N'Data copy completed ' + CAST (@@ROWCOUNT AS NVARCHAR (20)) + N' rows';

	-- 6. Swap table names & FIX PK NAMES
	DECLARE @Timestamp NVARCHAR (20) ;
	DECLARE @BackupName NVARCHAR (128) ;

	SET @Timestamp = CONVERT (VARCHAR (8) , GETDATE () , 112) + '_' + REPLACE (CONVERT (VARCHAR (8) , GETDATE () , 108) , ':', '') ;
	SET @BackupName = @TableName + '_Old_' + @Timestamp;

	-- 6.1 Rename Old Table to Backup Name
	EXEC sp_rename @OldTable, @BackupName;

	-- 6.2 Rename the PK on the Backup Table to free up the original name
	DECLARE @CurrentOldPKName NVARCHAR (128) ;
	DECLARE @NewBackupPKName NVARCHAR (128) ;

	-- Find the name of the PK currently attached to the backup table
	SELECT @CurrentOldPKName = name
	FROM sys.key_constraints
	WHERE parent_object_id = OBJECT_ID (@SchemaName + '.' + @BackupName) AND type = 'PK';

	IF @CurrentOldPKName IS NOT NULL
	BEGIN
		-- Rename it: e.g. PK_MyTable -> PK_MyTable_Old_2024...
		SET @NewBackupPKName = @CurrentOldPKName + '_Old_' + @Timestamp;
		DECLARE @FullOldPKName NVARCHAR (258) = @SchemaName + '.' + @CurrentOldPKName;
		-- Check if length exceeds limit (optional safety, usually fine) IF LEN (@NewBackupPKName) > 128 SET @NewBackupPKName = LEFT (@NewBackupPKName, 128) ;
		EXEC sp_rename @FullOldPKName, @NewBackupPKName, 'OBJECT';
	END

	-- 6.3 Rename New Table to Original Name
	EXEC sp_rename @NewTable, @TableName;

	-- 6.4 Rename New PK Temp -> Original PK Name
	DECLARE @OldTempPKName NVARCHAR (128) ;
	DECLARE @RealPKName NVARCHAR (128) ;
	SET @OldTempPKName = @SchemaName + '.PK_' + @TableName + '_Temp';
	SET @RealPKName = 'PK_' + @TableName;

	IF EXISTS (SELECT 1 FROM sys.key_constraints WHERE name LIKE 'PK_' + @TableName + '_Temp') EXEC sp_rename @OldTempPKName, @RealPKName, 'OBJECT';

	-- 7. Recreate FKs
	IF LEN (@CreateFK) > 0 EXEC sp_executesql @CreateFK;

	COMMIT TRANSACTION;
	PRINT N'';
	PRINT N'SUCCESS! Columns reordered.';
	PRINT N'-------------------------------------------------------';

END TRY
BEGIN CATCH
	IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
	PRINT N'ERROR: ' + ERROR_MESSAGE () ;
	THROW;
END CATCH;

การใช้ก็เปลี่ยนแค่ส่วนที่ไฮไลท์ไว้ เหมาะกับงานที่แก้ตารางกันบ่อย ๆ ถ้ามี column เยอะ ๆ ก็เอามาจากเรื่อง SQL Server: list columns เอามาใช้ ก็ได้เร็วกว่าเยอะเลย

อ่านเพิ่มเติม