May 23, 2014 at 2:08 pm
Comments posted to this topic are about the item Create MERGE statements with data!
______________________________________________________________________
Personal Motto: Why push the envelope when you can just open it?
If you follow the direction given HERE[/url] you'll likely increase the number and quality of responses you get to your question.
Jason L. SelburgMay 28, 2014 at 6:51 am
EDIT: In case this doesn't make it into the actual article, I wanted to clarify a few items.
1. The script has a few restricting conditions that you may want to remove. Specifically, the check for “IsActive” or “Active” columns.
2. The output, if copied and pasted into a new window should retain the proper formatting.
3. Your table MUST have a Primary Key defined.
4. Replacing the final select (that returns the statement) with the below code will handle >, < and & conversions caused by the use of XML datatype.
BEGIN
SELECT
REPLACE(REPLACE(REPLACE(REPLACE(@sqlTextFirst +
STUFF((SELECT
UsingList AS [text()]
FROM
#DataToMerge
FOR XML PATH(''))
,1,1,'')+
@sqlTextSecond
,'USING (VALUES ,(','USING (VALUES ('), '<', '<'), '>', '>'), '&', '&')
END
** The USING clause should look like this.
______________________________________________________________________
Personal Motto: Why push the envelope when you can just open it?
If you follow the direction given HERE[/url] you'll likely increase the number and quality of responses you get to your question.
Jason L. SelburgJuly 16, 2014 at 9:19 am
For those of you that would like a single procedure that doesn't depend upon the Extended Properties and will work for a single table.
Here's what we use:
/****** Object: StoredProcedure [Admin].[CreateMerge] Script Date: 9/3/2015 10:07:29 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
=============================================
Author: Jason L. Selburg
Create date: 07/25/2014
Description: Returns the data for the program income receivables report
- The 'Created','LastEdited','IsTiedToBL','TS' and GUID columns are ignored
- @ExcludeData is set to 1, then only the merge statement is created with a single row with placeholders
- @ReverseOrder and @TopCount are used when the data in the table exceeds the output length
- @ReverseOrder orders the data by the PK DESC
- @Topcount only return this number of rows. ONLY used when @ReverseOrder is set
- @CustomWhere to hold a custom where claues for tables with large amounts of data
*/
ALTER PROCEDURE [Admin].[CreateMerge]
(@TableName NVARCHAR(256)
,@Schemaname NVARCHAR(256) = N'dbo'
,@ExcludeData INT = 0
,@ReverseOrder INT = 0
,@TopCount INT = 10
,@CustomWhere NVARCHAR(4000) = ''
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@i INT
,@sqlTextFirst VARCHAR(MAX)
,@sqlTextSecond VARCHAR(MAX)
,@justDataList VARCHAR(MAX)
,@EXECText NVARCHAR(MAX)
,@MergeText VARCHAR(MAX)
,@Filename VARCHAR(256)
,@Debug INT = 0;
IF LEFT(@CustomWhere,6) <> 'WHERE ' AND LEN(@CustomWhere) > 1
BEGIN
SELECT 'Invalid WHERE Clause'
RETURN
END;
IF OBJECT_ID('tempdb..#DataToMerge') IS NOT NULL
DROP TABLE #DataToMerge
CREATE TABLE #DataToMerge
(nDex INT IDENTITY
,UsingList NVARCHAR(MAX));
WITH TableInfoStart
AS (SELECT DISTINCT
t.object_id
,s.name AS schemaName
,t.name AS tableName
,icc.name AS PK
,icc.is_identity AS PKisID
,STUFF((SELECT
',[' + c.Name + ']' AS [text()]
FROM
sys.COLUMNS AS c
WHERE
t.OBJECT_ID = c.OBJECT_ID
AND c.NAME NOT IN ('Created','LastEdited','IsTiedToBL','TS')
AND c.NAME NOT LIKE '%GUID'
FOR
XML PATH('')
),1,1,'') AS columnlist
,'''' + CHAR(10) + ' ,('' + ' + STUFF((SELECT
CASE WHEN st.precision = 0 OR
st.user_type_id IN (40,41,42,43,8,11,241,58,61,189,98) THEN ---- needs wrapped in quotes
' + '','' + ISNULL('''''''' + REPLACE([' + c.Name + '],'''''''','''''''''''') + '''''''',''NULL'')'
ELSE
' + '','' + ISNULL(REPLACE(CAST([' + c.Name + '] AS VARCHAR(MAX)),'''''''',''''''''''''),''NULL'')'
END AS [text()]
FROM
sys.COLUMNS AS c
INNER JOIN sys.types AS st
ON c.user_type_id = st.user_type_id
WHERE
t.OBJECT_ID = c.OBJECT_ID
AND c.NAME NOT IN ('Created','LastEdited','IsTiedToBL','TS')
AND c.NAME NOT LIKE '%GUID'
AND ( (c.name <> icc.name AND icc.is_identity = 1) OR icc.is_identity = 0)
FOR
XML PATH('')
),1,8,'') + ' + '')''' AS selectlist
,CHAR(10) + ' (' + STUFF((SELECT
CASE WHEN st.precision = 0 OR
st.user_type_id IN (40,41,42,43,8,11,241,58,61,189,98) THEN ---- needs wrapped in quotes
','''+ c.Name +''''
ELSE
','+ c.Name
END AS [text()]
FROM
sys.COLUMNS AS c
INNER JOIN sys.types AS st
ON c.user_type_id = st.user_type_id
WHERE
t.OBJECT_ID = c.OBJECT_ID
AND c.NAME NOT IN ('Created','LastEdited','IsTiedToBL','TS')
AND c.NAME NOT LIKE '%GUID'
FOR
XML PATH(''))
,1,1,'') + ') ---- Place your data here' AS justDatalist
,STUFF(STUFF((SELECT
' AND [TGT].[' + col.COLUMN_NAME + '] = ' + '[SRC].[' + col.COLUMN_NAME + ']' + CHAR(10) AS [text()]
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS Tab
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE Col
ON Col.Constraint_Name = Tab.Constraint_Name
AND Col.Table_Name = Tab.Table_Name
AND Col.CONSTRAINT_SCHEMA = Tab.TABLE_SCHEMA
WHERE
tab.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND tab.TABLE_SCHEMA = s.NAME
AND tab.TABLE_NAME = t.name
FOR
XML PATH('')
),1,8,''),1,1,'') AS JoinClause
FROM
sys.tables AS t
INNER JOIN sys.schemas AS s
ON t.schema_id = s.schema_id
INNER JOIN sys.indexes AS i
ON i.object_id = t.object_id
AND i.is_primary_key = 1
INNER JOIN sys.index_columns AS ic
ON i.object_id = ic.object_id
AND i.index_id = ic.index_id
INNER JOIN sys.columns AS icc
ON ic.column_id = icc.column_id
AND ic.object_id = icc.object_id
WHERE
s.name = @SchemaName
AND
t.name = @TableName)
,TableInfo
AS (SELECT
object_id
,schemaName
,tableName
,PK
,PKisID
,CASE WHEN PKisID=1 THEN REPLACE(columnList,'[' + PK + '],','') ELSE columnList END AS columnList
,CASE WHEN PKisID=1 THEN REPLACE(selectlist,'[' + PK + '],','') ELSE selectlist END AS selectlist
,CASE WHEN PKisID=1 THEN REPLACE(justDatalist,',(' + PK + ',','(') ELSE justDatalist END AS justDatalist
,JoinClause
FROM TableInfoStart)
SELECT
@FileName = 'C:\SupportingData\' + schemaName + '.' + tableName + '.sql'
,@sqlTextFirst =
'/*' + CHAR(10) +
schemaName + '.' + tableName + CHAR(10) +
'*/' + CHAR(10) + CHAR(10) +
' MERGE INTO [' + schemaName + '].[' + tableName + '] AS [TGT]' + CHAR(10) +
' USING (VALUES '
,@sqlTextSecond = CHAR(10) +
' ) AS [SRC] ' + CHAR(10) +
' (' + columnList + ')' + CHAR(10) +
' ON' + JoinClause + CHAR(10) +
' ---- When the record does not exist... INSERT' + CHAR(10) +
' WHEN NOT MATCHED BY TARGET THEN' + CHAR(10) +
' INSERT ' + CHAR(10) +
---- handle the PK being an identitiy
' (' + CASE WHEN PKisID=1 THEN REPLACE(columnList,'[' + PK + '],','') ELSE columnList END + ') ' + CHAR(10) +
' VALUES ' + CHAR(10) +
---- handle the PK being an identitiy
' (' + CASE WHEN PKisID=1 THEN REPLACE(columnList,'[' + PK + '],','') ELSE columnList END + ');' + CHAR(10) +
'GO' + CHAR(10)
,@justDataList = justDataList
,@EXECText = CHAR(10) + +
CASE WHEN @TopCount = 1 THEN 'SELECT TOP (' + CAST(@TopCount AS VARCHAR(9)) + ') ' ELSE 'SELECT ' END
+ ISNULL(selectList,'') + CHAR(10) +
'FROM [' + schemaName + '].[' + tableName + '] ' + ISNULL(@CustomWhere,'') + ' ORDER BY ' + ISNULL(PK,'1') +
CASE WHEN @ReverseOrder = 1 THEN ' DESC' ELSE ' ASC' END
FROM TableInfo AS s;
PRINT @exectext
IF @ExcludeData = 0
BEGIN
INSERT #DataToMerge
EXEC sp_executesql @EXECText
IF EXISTS (SELECT NULL FROM #DataToMerge AS dtm)
BEGIN
SELECT REPLACE(REPLACE(REPLACE(REPLACE(@sqlTextFirst +
STUFF(STUFF((SELECT
UsingList AS [text()]
FROM
#DataToMerge
FOR XML PATH(''))
,1,1,''), 12,1,CHAR(10) + ' ')+
@sqlTextSecond
,'USING (VALUES (','USING (VALUES ('), '<', '<'), '>', '>'), '&', '&')
END
ELSE
SELECT 'No Data'
END
ELSE
BEGIN
SELECT
REPLACE(@sqlTextFirst
+ @JustDataList
+ @sqlTextSecond ,'USING (VALUES (','USING (VALUES (')
END;
END
GO
______________________________________________________________________
Personal Motto: Why push the envelope when you can just open it?
If you follow the direction given HERE[/url] you'll likely increase the number and quality of responses you get to your question.
Jason L. SelburgSeptember 8, 2015 at 2:55 pm
Wow, this looks really risky.
September 9, 2015 at 6:36 am
Iwas Bornready (9/8/2015)
Wow, this looks really risky.
Risky? It doesn't run any code. It simply creates the code for you. We've used in production for over a year now and it works beautifully.
______________________________________________________________________
Personal Motto: Why push the envelope when you can just open it?
If you follow the direction given HERE[/url] you'll likely increase the number and quality of responses you get to your question.
Jason L. SelburgJune 13, 2017 at 1:44 pm
I tried the CreateMerge procedure since it might be useful.
The procedure however has a few issues:
[1] - the column order of the source is (sometimes?) alphabetically, and the identity column is not removed from the column list;
[2] - an identity column is omitted from the source, but when it's also the primary key the join clause is incorrect;
Table + dummy data (version: SQL Server 2012 SP3 CU9)CREATE TABLE dbo.languages (
language_id INT IDENTITY NOT NULL CONSTRAINT pk_languages PRIMARY KEY CLUSTERED,
isocode CHAR(2) NOT NULL,
[language] VARCHAR(100) NOT NULL,
in_use BIT NOT NULL CONSTRAINT df_languages_in_use DEFAULT 1);
INSERT INTO dbo.languages (isocode, [language], in_use)
VALUES
('en', 'English', 1),
('nl', 'Dutch', 1),
('pd', 'ProtoDutch (Dunglish)', 0);
EXEC [Admin].CreateMerge @TableName = 'languages'
The output:MERGE INTO [dbo].[languages] AS [TGT]
USING (VALUES
('en','English',1)
,('nl','Dutch',1)
,('pd','ProtoDutch (Dunglish)',0)
) AS [SRC]
([in_use],[isocode],[language],[language_id]) /* wrong order, and identity column not removed */
ON [TGT].[language_id] = [SRC].[language_id] /* problem */
---- When the record does not exist... Etcetera
Issue [1] can easily be fixed by adding:ORDER BY c.column_id
in:SELECT
',[' + c.Name + ']' AS [text()]
FROM sys.columns AS c
WHERE t.OBJECT_ID = c.OBJECT_ID
AND c.NAME NOT IN ('Created','LastEdited','IsTiedToBL','TS')
AND c.NAME NOT LIKE '%GUID'
ORDER BY c.column_id /* + */
FOR XML PATH('')
The new output: MERGE INTO [dbo].[languages] AS [TGT]
USING (VALUES
('en','English',1)
,('nl','Dutch',1)
,('pd','ProtoDutch (Dunglish)',0)
) AS [SRC]
([isocode],[language],[in_use]) /* better */
ON [TGT].[language_id] = [SRC].[language_id] /* .. but still a problem */
---- When the record does not exist... Etcetera
Issue [2]: For tables without an identity property the proc yielded in a few simple tests a correct MERGE statement. In other cases the join clause will have to be modified manually. Well, as the author said: "This script does NOT handle all situations." It's given me some food for thought. 🙂
June 13, 2017 at 4:10 pm
Thanks gserdijn for the catch and fix!
It's also good to hear it's inspired your thinking of other uses.
______________________________________________________________________
Personal Motto: Why push the envelope when you can just open it?
If you follow the direction given HERE[/url] you'll likely increase the number and quality of responses you get to your question.
Jason L. SelburgViewing 7 posts - 1 through 6 (of 6 total)
You must be logged in to reply to this topic. Login to reply