dnlnln / generate-sql-merge

Generate SQL MERGE statements with Table data

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

No rows in results generates invalid MERGE statement

dnlnln opened this issue · comments

Originally reported by @Dunnymeister in PR #26

Running sp_generate_merge on a table where there are no rows, or with a @from filter which returns no rows, created invalid SQL.

Steps to reproduce

Execute the following (AdventureWorks.dbo.ErrorLog table should have zero rows):

EXEC [AdventureWorks2014]..[sp_generate_merge] @table_name = 'ErrorLog',  @schema = 'dbo'

This generates the following T-SQL:

USE AdventureWorks2014
GO

SET NOCOUNT ON

SET IDENTITY_INSERT [dbo].[ErrorLog] ON

MERGE INTO [dbo].[ErrorLog] AS Target
USING (VALUES
) AS Source ([ErrorLogID],[ErrorTime],[UserName],[ErrorNumber],[ErrorSeverity],[ErrorState],[ErrorProcedure],[ErrorLine],[ErrorMessage])
ON (Target.[ErrorLogID] = Source.[ErrorLogID])
WHEN MATCHED AND (
	NULLIF(Source.[ErrorTime], Target.[ErrorTime]) IS NOT NULL OR NULLIF(Target.[ErrorTime], Source.[ErrorTime]) IS NOT NULL OR 
	NULLIF(Source.[UserName], Target.[UserName]) IS NOT NULL OR NULLIF(Target.[UserName], Source.[UserName]) IS NOT NULL OR 
	NULLIF(Source.[ErrorNumber], Target.[ErrorNumber]) IS NOT NULL OR NULLIF(Target.[ErrorNumber], Source.[ErrorNumber]) IS NOT NULL OR 
	NULLIF(Source.[ErrorSeverity], Target.[ErrorSeverity]) IS NOT NULL OR NULLIF(Target.[ErrorSeverity], Source.[ErrorSeverity]) IS NOT NULL OR 
	NULLIF(Source.[ErrorState], Target.[ErrorState]) IS NOT NULL OR NULLIF(Target.[ErrorState], Source.[ErrorState]) IS NOT NULL OR 
	NULLIF(Source.[ErrorProcedure], Target.[ErrorProcedure]) IS NOT NULL OR NULLIF(Target.[ErrorProcedure], Source.[ErrorProcedure]) IS NOT NULL OR 
	NULLIF(Source.[ErrorLine], Target.[ErrorLine]) IS NOT NULL OR NULLIF(Target.[ErrorLine], Source.[ErrorLine]) IS NOT NULL OR 
	NULLIF(Source.[ErrorMessage], Target.[ErrorMessage]) IS NOT NULL OR NULLIF(Target.[ErrorMessage], Source.[ErrorMessage]) IS NOT NULL) THEN
 UPDATE SET
  [ErrorTime] = Source.[ErrorTime], 
  [UserName] = Source.[UserName], 
  [ErrorNumber] = Source.[ErrorNumber], 
  [ErrorSeverity] = Source.[ErrorSeverity], 
  [ErrorState] = Source.[ErrorState], 
  [ErrorProcedure] = Source.[ErrorProcedure], 
  [ErrorLine] = Source.[ErrorLine], 
  [ErrorMessage] = Source.[ErrorMessage]
WHEN NOT MATCHED BY TARGET THEN
 INSERT([ErrorLogID],[ErrorTime],[UserName],[ErrorNumber],[ErrorSeverity],[ErrorState],[ErrorProcedure],[ErrorLine],[ErrorMessage])
 VALUES(Source.[ErrorLogID],Source.[ErrorTime],Source.[UserName],Source.[ErrorNumber],Source.[ErrorSeverity],Source.[ErrorState],Source.[ErrorProcedure],Source.[ErrorLine],Source.[ErrorMessage])
WHEN NOT MATCHED BY SOURCE THEN 
 DELETE
;
GO
DECLARE @mergeError int
 , @mergeCount int
SELECT @mergeError = @@ERROR, @mergeCount = @@ROWCOUNT
IF @mergeError != 0
 BEGIN
 PRINT 'ERROR OCCURRED IN MERGE FOR [dbo].[ErrorLog]. Rows affected: ' + CAST(@mergeCount AS VARCHAR(100)); -- SQL should always return zero rows affected
 END
ELSE
 BEGIN
 PRINT '[dbo].[ErrorLog] rows affected by MERGE: ' + CAST(@mergeCount AS VARCHAR(100));
 END
GO

SET IDENTITY_INSERT [dbo].[ErrorLog] OFF
GO
SET NOCOUNT OFF
GO

Execute the generated T-SQL to produce the following error:

Msg 102, Level 15, State 1, Line 15
Incorrect syntax near ')'.
ERROR OCCURRED IN MERGE FOR [dbo].[ErrorLog]. Rows affected: 0

A solution that has worked for a few people over on SO is to replace the VALUES clause of the source with a SELECT on the target table that returns an empty result set. This workaround would change the above (erroneous) MERGE statement from:

MERGE INTO [dbo].[ErrorLog] AS Target
USING (VALUES 
) AS Source ([ErrorLogID],[ErrorTime],[UserName],[ErrorNumber],[ErrorSeverity],[ErrorState],[ErrorProcedure],[ErrorLine],[ErrorMessage])
...

To the following (valid) T-SQL:

MERGE INTO [dbo].[ErrorLog] AS Target
USING (SELECT [ErrorLogID],[ErrorTime],[UserName],[ErrorNumber],[ErrorSeverity],[ErrorState],[ErrorProcedure],[ErrorLine],[ErrorMessage] FROM [dbo].[ErrorLog] WHERE 1 = 0 
) AS Source ([ErrorLogID],[ErrorTime],[UserName],[ErrorNumber],[ErrorSeverity],[ErrorState],[ErrorProcedure],[ErrorLine],[ErrorMessage])
...

Note that, if this T-SQL were to be substituted into the above sample MERGE statement and executed, this would result in all rows being deleted from the target table. This would be the correct behaviour, however, as the intention is to always have the target table reflect the state of the source dataset, even if it means emptying the target. The only rows that wouldn't be deleted are any that have been filtered out by a WHERE clause specified within the @from parameter (although you'll note in the above sp_generate_merge example, this parameter has not been specified).