Reputation: 870
Is it at all possible to have a table valued function in MSSQL that takes an attribute and generates an associated SQL statement with Pivot function?
CREATE FUNCTION dbo.fnPivot (@EntityTypeID int)
RETURNS TABLE
AS
BEGIN
DECLARE @SQL varchar(MAX);
DECLARE @COLS varchar(MAX);
select @COLS=coalesce(@COLS+',','')+'['+Name+']'from c_EntityAttribute WHERE EntityTypeID = @EntityTypeID;
SET @SQL = 'SELECT * FROM (SELECT EntityInstanceID, AttributeName, Value FROM v_EntityElementData WHERE EntityTypeID = 1) as s';
SET @SQL = @SQL + 'PIVOT ( MIN(Value) FOR AttributeName IN (' + @COLS + ') ) AS p';
RETURN EXEC sp_ExecuteSQL @SQL ;
END
Upvotes: 7
Views: 17929
Reputation: 12803
I've made the following procedure for dynamically pivoting all the values in a table.
CREATE OR ALTER PROC dbo.spDynamicPivot (
@Query NVARCHAR(MAX) --The query to pivot
, @AggregateFunction NVARCHAR(MAX) --The aggregation function surrounding the column name to be pivoted
, @ValueColumn NVARCHAR(MAX) --The value column from which to create columns
, @ResultTable NVARCHAR(MAX) = NULL --An optional table name into which the results will be stored. Note, this unfortunately will not work with #temp tables.
) AS BEGIN
SET XACT_ABORT ON;
SET NOCOUNT ON;
DECLARE @cols NVARCHAR(MAX);
DECLARE @sql NVARCHAR(MAX) = 'SELECT @cols = ISNULL(@cols + '','', '''') + ''['' + Val + '']'' FROM (SELECT DISTINCT Val = ' + @ValueColumn + ' FROM (' + @Query + ') a) b;';
DECLARE @paramDef NVARCHAR(MAX) = '@cols NVARCHAR(MAX) OUTPUT';
EXEC sp_executesql @sql, @paramDef, @cols = @cols OUTPUT;
DECLARE @resultSetSql NVARCHAR(MAX) = IIF(ISNULL(@ResultTable, '') <> '', 'INTO ' + @ResultTable + ' ', '');
SET @sql = 'SELECT * ' + @resultSetSql + 'FROM (' + @Query + ') q PIVOT( ' + @AggregateFunction + ' FOR ' + @ValueColumn + ' IN (' + @cols + ') ) p';
EXEC sp_executesql @sql;
END
Usage:
DROP TABLE IF EXISTS #t;
CREATE TABLE #t (Name VARCHAR(50), Age INT, NetWorth FLOAT);
INSERT INTO #t VALUES
('Bill', 50, 250),
('Barb', 17, 15),
('Fred', 25, 30),
('Bill', 25, 100),
('Kahless', 90000, 4E10)
--Displaying results directly
EXEC dbo.spDynamicPivot @Query = 'SELECT * FROM #t', @AggregateFunction = 'AVG(NetWorth)', @ValueColumn = 'Name'
--Or writing them into a table for additional use
DROP TABLE IF EXISTS tempdb.dbo.MyTable;
EXEC dbo.spDynamicPivot @Query = 'SELECT * FROM #t', @AggregateFunction = 'AVG(NetWorth)', @ValueColumn = 'Name', @ResultTable = 'tempdb.dbo.MyTable'
SELECT * FROM tempdb.dbo.MyTable
Upvotes: 1
Reputation: 16472
Here's something I did to accommodate this kind of feature (as a view, but a function would follow the same methodology). We stuck it at the end of every install script so when any changes were made to the table being pivoted, they would be picked up in the re-generated view.
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = Object_id(N'[dbo].[vwYourView]')
AND type IN ( N'V' ))
BEGIN
DROP VIEW [dbo].[vwYourView]
END
GO
Declare @cols VARCHAR(MAX)
Select @cols = COALESCE(@cols + ',[' + YourColumn+ ']',
'[' + YourColumn+ ']')
FROM YourTable
Order By YourColumn
DECLARE @query VARCHAR(MAX)
Set @query =
N'
Create View vwYourView
AS
Select * FROM YourTable
Pivot (
MAX(YourVal)
FOR YourColumn IN( '+
@cols
+')
) AS pvt
'
Execute(@query)
Select * FROM [vwYourView]
Upvotes: 2
Reputation: 46425
A table valued function must always return a specified schema, so this isn't possible.
Upvotes: 3
Reputation: 1316
Unfortunately not, with the exception of stored procedures SQL Server keeps track of the table definition of the output of all views, functions etc. Thus the columns and types can't change dynamically on the input.
For this reason I've never found the PIVOT operator to be useful. Generally the only way to get varying column data out is to treat it as XML.
For what reason are you pivoting it? This is usually a UI concern, and so I would recommend doing it at the UI, or SSRS etc.
Upvotes: 6