Technical Article

The dynamic unpivoter.

,

If you're ever stuck in a situation where you want to unpivot a table even a temp table this SP will solve your problems.

The sp takes 3 arguements.  The first being the name of the table that you want to unpivot, an optioinal second paramenter for the tableSchema.  By default the SP will use the dbo schema.  Finally, the third parameter is an output paramater to capture any errors.

example: exec [usp_dynamic_unpivot] '#test','dbo',''

There is only one prerequisite.  The table you want to unpivot must have primary key(s).  If not the SP will have no idea what to pivot on.  It needs the primary key to normalize the table.  The output is a table with the primary key(s) as columns and two additional columns called columnName and value.  The columnName column will contain all the pivoted column names and the value column will contain all the columnName values.

Since a null value can't be unpivoted all nulls are translated to a blank string.  Also, since the source of the data in the value column could be any datatype all datatypes are translated to a varchar(255) string.  Simply, update the SP if you need a longer string value.  Obvioulsy, you will not be able to unpivot a text, image, xml datatype datatype.

So, if you're ever stuck with having to compare a wide pivoted out table between two databases, simply run the unpivoter on each table, store the results in two temp tables.  Then you can simply join the two tables on the primary key and list out all the diffs.

-- =============================================
-- Description:	<This is it!! The dynamic UN-Pivoter>
-- =============================================
CREATE PROCEDURE [dbo].[usp_dynamic_unpivot]
( @tableName sysname,
  @tableSchema varchar(5) = 'dbo',
  @errorText varchar(1000) = '' OUTPUT
)
AS
BEGIN
	SET NOCOUNT ON
	SET ANSI_WARNINGS OFF

	DECLARE @SPID int
	SET @SPID = @@SPID  
	DECLARE @SERVERID nvarchar(128)
	SET @SERVERID = @@servername
	DECLARE @DBNAME VARCHAR(128)
	SET @DBNAME = DB_NAME()
	
	--ASSING USER VARIBALES, EXAMPLE IS DYNMAIC SQL VARIABLE BELOW
	DECLARE @sqlStatement nvarchar(max),
			@ColumnNames nvarchar(max),
			@Clean_ColumnNames nvarchar(max),
			@keyFields varchar(1000),
			@ParmDefinition nvarchar(500)
		
	SET @ParmDefinition = '@errorText nvarchar(max) output'
	
	BEGIN TRY
			
		IF LEFT(@tableName,1) = '#'
			BEGIN
				SELECT @keyFields =  COALESCE(@keyFields + ',' + quotename(a.COLUMN_NAME),quotename(a.COLUMN_NAME))
				FROM tempdb.INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE a join tempdb.INFORMATION_SCHEMA.COLUMNS b
					ON a.COLUMN_NAME = b.COLUMN_NAME
					AND a.TABLE_NAME = b.TABLE_NAME
					AND a.TABLE_SCHEMA = b.TABLE_SCHEMA
				WHERE a.TABLE_NAME like @tableName + '%'
				ORDER BY b.ORDINAL_POSITION
				--STR(19,6)		
				SELECT @ColumnNames = COALESCE(@ColumnNames + ',' + 
					CASE 
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,4) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME not like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,6) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						ELSE 
							'ISNULL(CAST(' + quotename(COLUMN_NAME) + ' AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						END,
					CASE 
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,4) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME not like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,6) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						ELSE 
							'ISNULL(CAST(' + quotename(COLUMN_NAME) + ' AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						END
				),
				@Clean_ColumnNames = COALESCE(@Clean_ColumnNames + ',' + quotename(COLUMN_NAME),quotename(COLUMN_NAME))
				FROM tempdb.INFORMATION_SCHEMA.COLUMNS
				WHERE TABLE_NAME like @tableName + '%'
				--AND TABLE_SCHEMA = @tableSchema
				AND COLUMN_NAME not in (SELECT COLUMN_NAME FROM tempdb.INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME like @tableName + '%')

				SET @sqlStatement = 'BEGIN TRY SELECT ' + @keyFields + ',columnName,value
				FROM 
				   (SELECT ' + @keyFields + ', ' + @ColumnNames + '
				   FROM ' + @tableName + ') bpim
				UNPIVOT
				   (value FOR columnName IN 
					  (' + @Clean_ColumnNames + ')
				)AS unpvt' + ' END TRY BEGIN CATCH SELECT @errorText=ERROR_MESSAGE() END CATCH'

				--SELECT @sqlStatement
				EXEC sp_executesql @sqlStatement,@ParmDefinition,@errorText=@errorText output
				IF ( len(@errorText) > 1)
					RAISERROR(@errorText,10,0)
			END
		ELSE
			BEGIN
				SELECT @keyFields =  COALESCE(@keyFields + ',' + quotename(a.COLUMN_NAME),quotename(a.COLUMN_NAME))
				FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE a join INFORMATION_SCHEMA.COLUMNS b
					ON a.COLUMN_NAME = b.COLUMN_NAME
					AND a.TABLE_NAME = b.TABLE_NAME
					AND a.TABLE_SCHEMA = b.TABLE_SCHEMA
				WHERE a.TABLE_NAME = @tableName
				AND a.TABLE_SCHEMA = @tableSchema
				ORDER BY b.ORDINAL_POSITION
						
				SELECT @ColumnNames = COALESCE(@ColumnNames + ',' + 
					CASE 
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,4) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME not like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,6) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						ELSE 
							'ISNULL(CAST(' + quotename(COLUMN_NAME) + ' AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						END,
					CASE 
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,4) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						WHEN DATA_TYPE = 'float' AND COLUMN_NAME not like '%Bal%' 
							THEN 'ISNULL(CAST(STR(' + quotename(COLUMN_NAME) + ',19,6) AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						ELSE 
							'ISNULL(CAST(' + quotename(COLUMN_NAME) + ' AS varchar(255)),'''') AS ' + quotename(COLUMN_NAME)
						END
				),				
				@Clean_ColumnNames = COALESCE(@Clean_ColumnNames + ',' + quotename(COLUMN_NAME),quotename(COLUMN_NAME))
				FROM INFORMATION_SCHEMA.COLUMNS
				WHERE TABLE_NAME = @tableName
				AND TABLE_SCHEMA = @tableSchema
				AND COLUMN_NAME not in (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME = @tableName AND TABLE_SCHEMA=@tableSchema)

				SET @sqlStatement = 'BEGIN TRY SELECT ' + @keyFields + ',columnName,value
				FROM 
				   (SELECT ' + @keyFields + ', ' + @ColumnNames + '
				   FROM ' + @tableSchema + '.' + @tableName + ') bpim
				UNPIVOT
				   (value FOR columnName IN 
					  (' + @Clean_ColumnNames + ')
				)AS unpvt' + ' END TRY BEGIN CATCH SELECT @errorText=ERROR_MESSAGE() END CATCH'
				
				EXEC sp_executesql @sqlStatement,@ParmDefinition,@errorText=@errorText output
				IF ( len(@errorText) > 1)
					RAISERROR(@errorText,10,0)
			END
		
	END TRY
	BEGIN CATCH
		SET @errorText = 'ERROR: ' + ISNULL(@errorText,ERROR_MESSAGE()) --isnull catches any sql syntax error from dynamic sql
		SET @errorText = @errorText + ' ON SP ==> ' + isnull(OBJECT_NAME(@@PROCID),'') + ',ON TABLE ==> ' + @tableName + ',ON SQL ==>' + @sqlStatement

		-- Test whether the transaction is committable.
		IF @@TRANCOUNT = 1
			ROLLBACK TRAN
		IF @@TRANCOUNT > 1
			COMMIT TRAN

		RAISERROR(@errorText,10,0,@SERVERID,@DBNAME,@SPID)
		--UP TO USER TO DECIDE HOW TO RETURN FROM NESTED TRANS
		RETURN	
	END CATCH
END

Rate

4.33 (3)

You rated this post out of 5. Change rating

Share

Share

Rate

4.33 (3)

You rated this post out of 5. Change rating