This could shave off some time in generating SQL Merge statement by hand. This helpful stored proc. will auto generate your merge statement on tables that use Type 0,1, and 2 SCD. The dim table just needs to be setup to use extended properties.
He uses the schema dim and etl to represent the dimension table and staging table, respectfully. The field IsRowCurrent is used to represent the current updated row.
http://www.purplefrogsystems.com/blog/2011/12/introduction-to-t-sql-merge-basics/
http://www.purplefrogsystems.com/blog/2012/01/using-t-sql-merge-to-load-data-warehouse-dimensions/
http://www.purplefrogsystems.com/blog/2012/04/automating-t-sql-merge-to-load-dimensions-scd/
Code Snippet
- /***************************************************************
- * *
- * Script for use with blog post *
- * "Automating T-SQL Merge to load Dimensions (SCD)" *
- * http://www.purplefrogsystems.com/blog/2012/04/automating-t-sql-merge-to-load-dimensions-scd
- * *
- * Posted: 6th April 2012 *
- * *
- * By: Alex Whittles - Purple Frog Business Intelligence *
- * www.PurpleFrogSystems.com *
- * *
- * All code samples are provided “AS IS” without warranty of *
- * any kind, either express or implied, including but not *
- * limited to the implied warranties of merchantability *
- * and/or fitness for a particular purpose. *
- * *
- ***************************************************************/
-
- --The generate merge proc
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GenerateMerge]') AND type in (N'P', N'PC'))
- DROP PROCEDURE [dbo].[GenerateMerge]
- GO
-
- CREATE PROCEDURE [dbo].[GenerateMerge]
- @Dimension varchar(50),
- @Schema varchar(50)='dim',
- @ETLSchema varchar(50)='etl',
- @Execute bit=0 --Should the resulting merge be returned or executed
- AS
- BEGIN
- SET NOCOUNT ON;
-
- --Create Carriage return variable to help format the resulting query
- DECLARE @crlf char(2)
- SET @crlf = CHAR(13)
-
-
- --Find out which Audit fields are used
- DECLARE @UseIsInferred bit
- DECLARE @UseValidFrom bit
- DECLARE @UseValidTo bit
- DECLARE @UseIsRowCurrent bit
- DECLARE @UseLastUpdated bit
-
- SET @UseIsInferred = ISNULL((SELECT MAX(1)
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND c.name = 'IsInferred'
- ),0)
-
- SET @UseValidFrom= ISNULL((SELECT MAX(1)
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND c.name = 'ValidFrom'
- ),0)
-
- SET @UseValidTo = ISNULL((SELECT MAX(1)
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND c.name = 'ValidTo'
- ),0)
-
- SET @UseIsRowCurrent = ISNULL((SELECT MAX(1)
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND c.name = 'IsRowCurrent'
- ),0)
-
- SET @UseLastUpdated = ISNULL((SELECT MAX(1)
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND c.name = 'LastUpdated'
- ),0)
-
-
- --Identify the business key column(s)
- --Also define what the null replacement should be
- DECLARE myCurBK Cursor FOR
- SELECT c.name
- , CASE WHEN ty.name IN ('text', 'ntext', 'varchar', 'char', 'nvarchar', 'nchar') THEN ''''''
- WHEN ty.name IN ('tinyint', 'smallint', 'int', 'real', 'money', 'float', 'bit', 'decimal', 'numeric','smallmoney','bigint') THEN '0'
- WHEN ty.name IN ('date', 'datetime') THEN '''19000101'''
- ELSE 'NULL' END AS NullRep
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id
- INNER JOIN sys.extended_properties ep
- ON t.object_id=ep.major_id
- AND c.column_id=ep.minor_id
- AND ep.class=1
- AND ep.name='SCD'
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND ep.value = 'BK'
- ORDER BY c.column_id
-
- --Identify all fields to be merged (Exclude Type 0)
- DECLARE myCurType1 Cursor
- FOR SELECT c.name
- , CASE WHEN ty.name IN ('text', 'ntext', 'varchar', 'char', 'nvarchar', 'nchar') THEN ''''''
- WHEN ty.name IN ('tinyint', 'smallint', 'int', 'real', 'money', 'float', 'bit', 'decimal', 'numeric','smallmoney','bigint') THEN '0'
- WHEN ty.name IN ('date', 'datetime') THEN '''19000101'''
- ELSE 'NULL' END AS NullRep
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id
- LEFT JOIN sys.extended_properties ep
- ON t.object_id=ep.major_id
- AND c.column_id=ep.minor_id
- AND ep.class=1
- AND ep.name='SCD'
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND c.is_identity=0
- AND ISNULL(ep.value,'1') NOT IN ('0', 'Audit', 'BK')
- ORDER BY c.column_id ASC
-
- --Identify all fields for insert
- DECLARE myCurAll Cursor
- FOR SELECT c.name
- , CASE WHEN ty.name IN ('text', 'ntext', 'varchar', 'char', 'nvarchar', 'nchar') THEN ''''''
- WHEN ty.name IN ('tinyint', 'smallint', 'int', 'real', 'money', 'float', 'bit', 'decimal', 'numeric','smallmoney','bigint') THEN '0'
- WHEN ty.name IN ('date', 'datetime') THEN '''19000101'''
- ELSE 'NULL' END AS NullRep
- FROM sys.columns c
- INNER JOIN sys.tables t on c.object_id = t.object_id
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
- INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id
- WHERE s.name = @Schema
- AND t.name = @Dimension
- AND c.name NOT IN ('LastUpdated', 'IsInferred', 'ValidFrom', 'ValidTo', 'IsRowCurrent')
- AND c.is_identity=0
- ORDER BY c.column_id ASC
-
- DECLARE @Field varchar(255)
- DECLARE @NullRep varchar(20)
-
- DECLARE @SQL varchar(max)
- DECLARE @SQL2 varchar(max)
- DECLARE @SQL3 varchar(max)
-
- --Now start building up the dynamic SQL
-
- SET @SQL = 'MERGE [' + @Schema + '].[' + @Dimension + '] AS Target'
- SET @SQL = @SQL + @crlf + ' USING [' + @ETLSchema + '].[' + @Dimension + '] AS Source'
-
- OPEN myCurBK
- FETCH NEXT FROM myCurBK INTO @Field, @NullRep
- IF (@@FETCH_STATUS>=0)
- BEGIN
- SET @SQL = @SQL + @crlf + ' ON Target.' + @Field + ' = Source.' + @Field
- FETCH NEXT FROM myCurBK INTO @Field, @NullRep
- END
- WHILE (@@FETCH_STATUS<>-1)
- BEGIN
- IF (@@FETCH_STATUS<>-2)
- SET @SQL = @SQL + @crlf + ' AND Target.' + @Field + ' = Source.' + @Field
- FETCH NEXT FROM myCurBK INTO @Field, @NullRep
- END
- CLOSE myCurBK
-
- IF @UseIsRowCurrent>0 SET @SQL = @SQL + @crlf + ' AND Target.IsRowCurrent=1'
-
- SET @SQL = @SQL + @crlf + ' WHEN MATCHED'
-
- OPEN myCurType1
- FETCH NEXT FROM myCurType1 INTO @Field, @NullRep
-
- IF (@@FETCH_STATUS>=0)
- BEGIN
- SET @SQL = @SQL + @crlf + ' AND (ISNULL(Target.' + @Field + ',' + @NullRep + ') <> ISNULL(Source.' + @Field + ',' + @NullRep + ')'
- FETCH NEXT FROM myCurType1 INTO @Field, @NullRep
- END
- WHILE (@@FETCH_STATUS<>-1)
- BEGIN
- IF (@@FETCH_STATUS<>-2)
- SET @SQL = @SQL + @crlf + ' OR ISNULL(Target.' + @Field + ',' + @NullRep + ') <> ISNULL(Source.' + @Field + ',' + @NullRep + ')'
- FETCH NEXT FROM myCurType1 INTO @Field, @NullRep
- END
- CLOSE myCurType1
-
- SET @SQL = @SQL + @crlf + ' )'
- SET @SQL2 = ' THEN UPDATE SET'
-
- OPEN myCurType1
- FETCH NEXT FROM myCurType1 INTO @Field, @NullRep
- IF (@@FETCH_STATUS>=0)
- BEGIN
- SET @SQL2 = @SQL2 + @crlf + ' ' + @Field + ' = Source.' + @Field
- FETCH NEXT FROM myCurType1 INTO @Field, @NullRep
- END
- WHILE (@@FETCH_STATUS<>-1)
- BEGIN
- IF (@@FETCH_STATUS<>-2)
- SET @SQL2 = @SQL2 + @crlf + ' ,' + @Field + ' = Source.' + @Field
- FETCH NEXT FROM myCurType1 INTO @Field, @NullRep
- END
- CLOSE myCurType1
-
- IF @UseLastUpdated>0 SET @SQL2 = @SQL2 + @crlf + ' ,LastUpdated = GetDate()'
-
- SET @SQL3 = ' WHEN NOT MATCHED THEN'
- SET @SQL3 = @SQL3 + @crlf + ' INSERT ('
-
- OPEN myCurAll
- FETCH NEXT FROM myCurAll INTO @Field, @NullRep
- IF (@@FETCH_STATUS>=0)
- BEGIN
- SET @SQL3 = @SQL3 + @crlf + ' ' + @Field
- FETCH NEXT FROM myCurAll INTO @Field, @NullRep
- END
- WHILE (@@FETCH_STATUS<>-1)
- BEGIN
- IF (@@FETCH_STATUS<>-2)
- SET @SQL3 = @SQL3 + @crlf + ' ,' + @Field
- FETCH NEXT FROM myCurAll INTO @Field, @NullRep
- END
- CLOSE myCurAll
-
-
- IF @UseIsInferred>0 SET @SQL3 = @SQL3 + @crlf + ' ,IsInferred'
- IF @UseValidFrom>0 SET @SQL3 = @SQL3 + @crlf + ' ,ValidFrom'
- IF @UseValidTo>0 SET @SQL3 = @SQL3 + @crlf + ' ,ValidTo'
- IF @UseIsRowCurrent>0 SET @SQL3 = @SQL3 + @crlf + ' ,IsRowCurrent'
- IF @UseLastUpdated>0 SET @SQL3 = @SQL3 + @crlf + ' ,LastUpdated'
- SET @SQL3 = @SQL3 + @crlf + ' ) VALUES ('
-
- OPEN myCurAll
- FETCH NEXT FROM myCurAll INTO @Field, @NullRep
- IF (@@FETCH_STATUS>=0)
- BEGIN
- SET @SQL3 = @SQL3 + @crlf + ' Source.' + @Field
- FETCH NEXT FROM myCurAll INTO @Field, @NullRep
- END
- WHILE (@@FETCH_STATUS<>-1)
- BEGIN
- IF (@@FETCH_STATUS<>-2)
- SET @SQL3 = @SQL3 + @crlf + ' ,Source.' + @Field
- FETCH NEXT FROM myCurAll INTO @Field, @NullRep
- END
- CLOSE myCurAll
-
- IF @UseIsInferred>0 SET @SQL3 = @SQL3 + @crlf + ' ,0'
- IF @UseValidFrom>0 SET @SQL3 = @SQL3 + @crlf + ' ,GetDate()'
- IF @UseValidTo>0 SET @SQL3 = @SQL3 + @crlf + ' ,NULL'
- IF @UseIsRowCurrent>0 SET @SQL3 = @SQL3 + @crlf + ' ,1'
- IF @UseLastUpdated>0 SET @SQL3 = @SQL3 + @crlf + ' ,GetDate()'
- SET @SQL3 = @SQL3 + @crlf + ' );'
-
- --clean up
- DEALLOCATE myCurType1
- DEALLOCATE myCurAll
- DEALLOCATE myCurBK
-
- IF @Execute = 1
- BEGIN
- EXEC(@SQL + @SQL2 + @SQL3)
- END
- ELSE
- BEGIN
- PRINT @SQL
- PRINT @SQL2
- PRINT @SQL3
- END
-
- END
-
- GO
An example of the extended properties being set through sql statement:
Code Snippet
- EXEC sys.sp_addextendedproperty @level2name=N'CustomerKey', @value=N'PK' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'Email', @value=N'BK' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'FirstName', @value=N'1' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'LastName', @value=N'1' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'DoB', @value=N'1' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'Sex', @value=N'1' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'MaritalStatus',@value=N'1' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'FirstCreated', @value=N'1' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'ValidFrom', @value=N'Audit' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'ValidTo', @value=N'Audit' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'IsRowCurrent', @value=N'Audit' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
- EXEC sys.sp_addextendedproperty @level2name=N'LastUpdated', @value=N'Audit' ,
- @name=N'SCD', @level0type=N'SCHEMA',@level0name=N'Dim',
- @level1type=N'TABLE',@level1name=N'Customer', @level2type=N'COLUMN'
An example of printing the script:
Code Snippet
- EXEC dbo.GenerateMerge
- @Dimension = 'Customer',
- @Schema = 'dim',
- @ETLSchema = 'etl',
- @Execute = 0