Funzione SQL che restituisce dati con pivot dinamico
Domanda
È possibile avere una funzione con valori di tabella in MSSQL che accetta un attributo e genera un'istruzione SQL associata con la funzione Pivot?
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
Soluzione
Sfortunatamente no, ad eccezione delle procedure memorizzate, SQL Server tiene traccia della definizione della tabella dell'output di tutte le viste, funzioni, ecc. Pertanto le colonne e i tipi non possono cambiare dinamicamente sull'input.
Per questo motivo non ho mai trovato utile l'operatore PIVOT. Generalmente l'unico modo per ottenere dati di colonne variabili è di trattarli come XML.
Per quale motivo lo stai ruotando? Di solito si tratta di un problema relativo all'interfaccia utente, quindi consiglierei di farlo nell'interfaccia utente o SSRS ecc.
Altri suggerimenti
Una funzione con valori di tabella deve sempre restituire uno schema specificato, quindi questo non è possibile.
Ecco qualcosa che ho fatto per accogliere questo tipo di funzionalità (come vista, ma una funzione seguirebbe la stessa metodologia). Lo abbiamo bloccato alla fine di ogni script di installazione, quindi quando venivano apportate modifiche alla tabella che veniva ruotata, venivano raccolte nella vista rigenerata.
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]
Ho eseguito la seguente procedura per ruotare dinamicamente tutti i valori in una tabella.
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
Utilizzo:
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