Skip to content

Instantly share code, notes, and snippets.

@lafleurh
Last active December 20, 2019 21:43
Show Gist options
  • Save lafleurh/614b168b253fcaddb926b1ddcbcbc840 to your computer and use it in GitHub Desktop.
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 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
@lafleurh
Copy link
Author

lafleurh commented Dec 4, 2019

Updated to use a temp table with identity to batch/merge.

@lafleurh
Copy link
Author

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