Last active
December 20, 2019 21:43
-
-
Save lafleurh/614b168b253fcaddb926b1ddcbcbc840 to your computer and use it in GitHub Desktop.
Generate a MS SQL Server merge statement to insert or update all rows in a table
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- This will generate Microsoft TSQL (MS SQL Server) in order to merge table records given a specific single-column key. | |
SET NOCOUNT ON | |
DECLARE @UseTempTable bit = 0 | |
DECLARE @ColumnList NVARCHAR(MAX); | |
DECLARE @SColumnList NVARCHAR(MAX); | |
DECLARE @SetColumnList NVARCHAR(MAX); | |
DECLARE @SelectValueList NVARCHAR(MAX); | |
DECLARE @WhereCond NVARCHAR(MAX) = '' -- ex. ' WHERE Ident = ''4375'''; | |
DECLARE @TableName NVARCHAR(30) = 'tablename'; | |
DECLARE @TableKey NVARCHAR(30) = 'tablekey'; | |
DECLARE @TempTableName NVARCHAR(30) = '#TEMP_' + @TableName | |
SELECT @ColumnList = "text" | |
FROM | |
( | |
SELECT CASE WHEN ROW_NUMBER() OVER (ORDER BY C.column_id ) > 1 THEN ',' ELSE '' END + C.Name AS "text()" | |
FROM sys.tables T INNER JOIN sys.columns C ON T.object_id = C.object_id | |
WHERE T.name = @TableName | |
FOR XML PATH ('') | |
) JOINTB(text) | |
SELECT @SColumnList = "text" | |
FROM | |
( | |
SELECT CASE WHEN ROW_NUMBER() OVER (ORDER BY C.column_id ) > 1 THEN ',' ELSE '' END + 'S.' + C.Name AS "text()" | |
FROM sys.tables T INNER JOIN sys.columns C ON T.object_id = C.object_id | |
WHERE T.name = @TableName | |
FOR XML PATH ('') | |
) JOINTB(text) | |
SELECT @SetColumnList = "text" | |
FROM | |
( | |
SELECT CASE WHEN ROW_NUMBER() OVER (ORDER BY C.column_id ) > 1 THEN ',' ELSE '' END + 'T.' + C.Name + ' = S.' + C.Name AS "text()" | |
FROM sys.tables T INNER JOIN sys.columns C ON T.object_id = C.object_id | |
WHERE T.name = @TableName AND C.name <> @TableKey | |
FOR XML PATH ('') | |
) JOINTB(text) | |
SELECT @SelectValueList = "text" | |
FROM ( | |
SELECT | |
CASE WHEN ROW_NUMBER() OVER (ORDER BY C.column_id ) > 1 THEN ' + ' ELSE '' END + | |
CASE WHEN ROW_NUMBER() OVER (ORDER BY C.column_id ) > 1 THEN ''','' + ' ELSE '' END + | |
'CASE WHEN ' + C.name + ' IS NULL THEN ''NULL'' ELSE ' + | |
CASE WHEN system_type_id IN (167) THEN ''''''''' +' | |
WHEN system_type_id IN (58, 61, 40) THEN ''''''''' + CAST(' | |
WHEN system_type_id IN (231) THEN '''N'''''' +' -- NVARCHAR | |
WHEN system_type_id IN (56, 104, 106, 48, 60, 52) THEN 'CAST(' | |
ELSE '!' + CAST(system_type_id AS NVARCHAR(10)) END -- Generate an error. | |
+ C.name + | |
CASE WHEN system_type_id IN (167) THEN '+ ''''''''' | |
WHEN system_type_id IN (58, 61, 40) THEN ' AS NVARCHAR(50)) + ''''''''' | |
WHEN system_type_id IN (231) THEN '+ ''''''''' -- NVARCHAR | |
WHEN system_type_id IN (56, 104, 106, 48, 60, 52) THEN ' AS NVARCHAR(50))' | |
ELSE '!' + CAST(system_type_id AS NVARCHAR(10)) END -- Generate an error. | |
+ ' END' | |
AS "text()" | |
FROM sys.tables T INNER JOIN sys.columns C ON T.object_id = C.object_id | |
WHERE T.name = @TableName | |
FOR XML PATH ('') | |
) JOINTB(text); | |
DECLARE @SelData NVARCHAR(MAX); | |
SET @SelData = 'SELECT ' + @SelectValueList + ' FROM ' + @TableName + @WhereCond | |
PRINT @SelData | |
DECLARE @Rows AS TABLE (data NVARCHAR(MAX)) | |
INSERT INTO @Rows | |
EXECUTE (@SelData) | |
DECLARE @MergeSql TABLE (seq int IDENTITY, mergecmd NVARCHAR(MAX)); | |
IF @UseTempTable = 1 | |
BEGIN | |
INSERT INTO @MergeSQL | |
SELECT 'SELECT TOP 0 * INTO ' + @TempTableName + ' FROM ' + @TableName + ';' ; | |
INSERT INTO @MergeSQL SELECT 'GO' | |
INSERT INTO @MergeSQL SELECT 'SET IDENTITY_INSERT ' + @TempTableName + ' ON;' | |
INSERT INTO @MergeSQL SELECT 'GO' | |
INSERT INTO @MergeSQL SELECT ' WITH record (' + @ColumnList + ') AS ( SELECT ' + data + ') INSERT INTO ' + @TempTableName + ' (' + @ColumnList + ') SELECT * FROM record;' | |
FROM @Rows | |
INSERT INTO @MergeSQL SELECT 'SET IDENTITY_INSERT ' + @TempTableName + ' OFF;' | |
INSERT INTO @MergeSQL SELECT 'GO' | |
INSERT INTO @MergeSQL SELECT 'SET IDENTITY_INSERT ' + @TableName + ' ON;' | |
INSERT INTO @MergeSQL SELECT 'GO' | |
INSERT INTO @MergeSQL SELECT ' WITH record (' + @ColumnList + ') AS ( SELECT * FROM ' + @TempTableName + ') | |
MERGE ' + @TableName + ' WITH (HOLDLOCK) T | |
USING Record S ON T.' + @TableKey + ' = S.' + @TableKey + ' | |
WHEN NOT MATCHED BY TARGET THEN | |
INSERT (' + @ColumnList + ') | |
VALUES (' + @SColumnList + ') | |
WHEN NOT MATCHED BY SOURCE THEN | |
DELETE | |
WHEN MATCHED THEN | |
UPDATE SET | |
' + @SetColumnList + ';' | |
END | |
ELSE | |
BEGIN | |
INSERT INTO @MergeSQL SELECT ' WITH record (' + @ColumnList + ') AS ( SELECT ' + data + ') ' + ' | |
MERGE ' + @TableName + ' WITH (HOLDLOCK) T | |
USING Record S ON T.' + @TableKey + ' = S.' + @TableKey + ' | |
WHEN NOT MATCHED THEN | |
INSERT (' + @ColumnList + ') | |
VALUES (' + @SColumnList + ') | |
WHEN MATCHED THEN | |
UPDATE SET | |
' + @SetColumnList + ';' | |
FROM @Rows | |
END; | |
IF @UseTempTable = 1 | |
BEGIN | |
INSERT INTO @MergeSQL SELECT 'DROP TABLE ' + @TempTableName + ';'; | |
INSERT INTO @MergeSQL SELECT 'GO' | |
INSERT INTO @MergeSQL SELECT 'SET IDENTITY_INSERT ' + @TableName + ' OFF;' | |
INSERT INTO @MergeSQL SELECT 'GO' | |
END; | |
SELECT mergecmd FROM @MergeSql ORDER BY seq | |
Fixed case statement for smallint. Was putting in quotes.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated to use a temp table with identity to batch/merge.