Konvertieren Sie Zeilen mit 'Pivot' in SQL Server in Spalten

279

Ich habe das Zeug auf MS-Pivot-Tabellen gelesen und habe immer noch Probleme, dies richtig zu machen.

Ich habe eine temporäre Tabelle, die gerade erstellt wird. Wir werden sagen, dass Spalte 1 eine Geschäftsnummer ist und Spalte 2 eine Wochennummer und schließlich Spalte 3 eine Gesamtheit eines Typs. Auch die Wochennummern sind dynamisch, die Geschäftsnummern sind statisch.

Store      Week     xCount
-------    ----     ------
102        1        96
101        1        138
105        1        37
109        1        59
101        2        282
102        2        212
105        2        78
109        2        97
105        3        60
102        3        123
101        3        220
109        3        87

Ich möchte, dass es wie folgt als Pivot-Tisch herauskommt:

Store        1          2          3        4        5        6....
----- 
101        138        282        220
102         96        212        123
105         37        
109

Speichern Sie die Zahlen unten und die Wochen oben.

Lynn
quelle
1
Mögliches Duplikat der dynamischen PIVOT-Abfrage
RichardTheKiwi

Antworten:

356

Wenn Sie SQL Server 2005+ verwenden, können Sie die PIVOTFunktion verwenden, um die Daten aus Zeilen in Spalten umzuwandeln.

Es hört sich so an, als müssten Sie dynamisches SQL verwenden, wenn die Wochen unbekannt sind, aber es ist einfacher, den richtigen Code zunächst mit einer fest codierten Version zu sehen.

Hier sind zunächst einige schnelle Tabellendefinitionen und Daten zur Verwendung:

CREATE TABLE #yt 
(
  [Store] int, 
  [Week] int, 
  [xCount] int
);

INSERT INTO #yt
(
  [Store], 
  [Week], [xCount]
)
VALUES
    (102, 1, 96),
    (101, 1, 138),
    (105, 1, 37),
    (109, 1, 59),
    (101, 2, 282),
    (102, 2, 212),
    (105, 2, 78),
    (109, 2, 97),
    (105, 3, 60),
    (102, 3, 123),
    (101, 3, 220),
    (109, 3, 87);

Wenn Ihre Werte bekannt sind, codieren Sie die Abfrage fest:

select *
from 
(
  select store, week, xCount
  from yt
) src
pivot
(
  sum(xcount)
  for week in ([1], [2], [3])
) piv;

Siehe SQL-Demo

Wenn Sie dann die Wochennummer dynamisch generieren müssen, lautet Ihr Code:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(Week) 
                    from yt
                    group by Week
                    order by Week
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT store,' + @cols + ' from 
             (
                select store, week, xCount
                from yt
            ) x
            pivot 
            (
                sum(xCount)
                for week in (' + @cols + ')
            ) p '

execute(@query);

Siehe SQL-Demo .

Die dynamische Version generiert die Liste der weekZahlen, die in Spalten konvertiert werden sollen. Beide ergeben das gleiche Ergebnis:

| STORE |   1 |   2 |   3 |
---------------------------
|   101 | 138 | 282 | 220 |
|   102 |  96 | 212 | 123 |
|   105 |  37 |  78 |  60 |
|   109 |  59 |  97 |  87 |
Taryn
quelle
4
Sehr schön! Aber wie kann man eine Spalte entfernen, wenn alle Werte dieser Spalte NULL sind?
ZooZ
1
@ZooZ Siehe Antwort unten . Ich habe es nicht wörtlich ausprobiert, aber das Konzept ist solide.
Ruffin
1
+1 "Es hört sich so an, als müssten Sie dynamisches SQL verwenden, wenn die Wochen unbekannt sind, aber es ist einfacher, den richtigen Code zunächst mit einer Hard-Cded-Version zu sehen." Im Gegensatz zur generischen Qlikview-Funktion ( community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic ), die dies ermöglicht, müssen Sie die verschiedenen "FOR ____ IN (...)"
The Red Pea
1
Wenn Sie zuvor eine Pivot-Tabelle mit einem cte erstellen. cte3 AS (select ... )dann haben Sie die oben definierte Logik mit dem @colsund @query... es liegt ein Fehler vor. "Ungültiger Objektname 'cte3'." Wie beheben Sie das? -
Elizabeth
3
Das ist fantastisch - schön @bluefeet. Ich hatte noch nie STUFF(...)zuvor (oder die XML PATHbeiden) verwendet. Zum Nutzen anderer Leser müssen lediglich die Spaltennamen zusammengefügt und das führende Komma abgeschnitten werden. Hinweis Ich denke, das Folgende ist etwas einfacher: Wählen Sie @cols = (SELECT DISTINCT QUOTENAME (Week) + ',' aus Ihrer Reihenfolge um 1 FOR XML PATH ('')) set @cols = SUBSTRING (@cols, 1, LEN ( @cols) - 1) ... Ersetzen des group byby distinctund order by 1und manuelles Hacken eines angehängten Kommas!
DarthPablo
26

Dies gilt für eine dynamische Anzahl von Wochen.

Vollständiges Beispiel hier: SQL Dynamic Pivot

DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)

--Get distinct values of the PIVOT Column 
SELECT @ColumnName= ISNULL(@ColumnName + ',','') + QUOTENAME(Week)
FROM (SELECT DISTINCT Week FROM #StoreSales) AS Weeks

--Prepare the PIVOT query using the dynamic 
SET @DynamicPivotQuery = 
  N'SELECT Store, ' + @ColumnName + ' 
    FROM #StoreSales
    PIVOT(SUM(xCount) 
          FOR Week IN (' + @ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql @DynamicPivotQuery
Enkode
quelle
Hey, ich habe eine Geige, bei der ich Tische dynamisch schwenken muss. Glaubst du, du kannst mir dabei helfen? dbfiddle.uk/…
Silly Volley
@SillyVolley hier ist eine, Sie haben nicht angegeben, worauf Sie schwenken wollten. Ich weiß auch nicht, ob Sie dies in Postgres tun können, also habe ich es in SQL Server getan
Enkode
16

Ich habe das gleiche schon einmal mit Unterabfragen erreicht. Wenn Ihre ursprüngliche Tabelle StoreCountsByWeek heißt und Sie eine separate Tabelle mit den Store-IDs haben, sieht sie folgendermaßen aus:

SELECT StoreID, 
    Week1=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=1),
    Week2=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=2),
    Week3=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=3)
FROM Store
ORDER BY StoreID

Ein Vorteil dieser Methode besteht darin, dass die Syntax klarer ist und es einfacher ist, sich mit anderen Tabellen zu verbinden, um auch andere Felder in die Ergebnisse zu ziehen.

Meine anekdotischen Ergebnisse sind, dass diese Abfrage über ein paar tausend Zeilen ausgeführt wurde, die in weniger als einer Sekunde abgeschlossen wurden, und ich hatte tatsächlich 7 Unterabfragen. Wie in den Kommentaren erwähnt, ist es jedoch rechenintensiver, dies auf diese Weise zu tun. Seien Sie also vorsichtig bei der Verwendung dieser Methode, wenn Sie erwarten, dass sie mit großen Datenmengen ausgeführt wird.

Eric Barr
quelle
8
Es ist einfacher, aber es ist eine sehr teure Operation. Diese Unterabfragen müssen einmal für jede von der Tabelle zurückgegebene Zeile ausgeführt werden.
Greg
11

Folgendes können Sie tun:

SELECT * 
FROM yourTable
PIVOT (MAX(xCount) 
       FOR Week in ([1],[2],[3],[4],[5],[6],[7])) AS pvt

DEMO

Praveen Nambiar
quelle
5

Ich schreibe ein sp, das für diesen Zweck nützlich sein könnte. Im Grunde dreht dieses sp jede Tabelle und gibt eine neue Tabelle zurück, die geschwenkt wird, oder gibt nur den Datensatz zurück. Dies ist die Art und Weise, wie es ausgeführt wird:

Exec dbo.rs_pivot_table @schema=dbo,@table=table_name,@column=column_to_pivot,@agg='sum([column_to_agg]),avg([another_column_to_agg]),',
        @sel_cols='column_to_select1,column_to_select2,column_to_select1',@new_table=returned_table_pivoted;

Bitte beachten Sie, dass im Parameter @agg die Spaltennamen mit sein müssen '['und der Parameter mit einem Komma enden muss','

SP

Create Procedure [dbo].[rs_pivot_table]
    @schema sysname=dbo,
    @table sysname,
    @column sysname,
    @agg nvarchar(max),
    @sel_cols varchar(max),
    @new_table sysname,
    @add_to_col_name sysname=null
As
--Exec dbo.rs_pivot_table dbo,##TEMPORAL1,tip_liq,'sum([val_liq]),sum([can_liq]),','cod_emp,cod_con,tip_liq',##TEMPORAL1PVT,'hola';
Begin

    Declare @query varchar(max)='';
    Declare @aggDet varchar(100);
    Declare @opp_agg varchar(5);
    Declare @col_agg varchar(100);
    Declare @pivot_col sysname;
    Declare @query_col_pvt varchar(max)='';
    Declare @full_query_pivot varchar(max)='';
    Declare @ind_tmpTbl int; --Indicador de tabla temporal 1=tabla temporal global 0=Tabla fisica

    Create Table #pvt_column(
        pivot_col varchar(100)
    );

    Declare @column_agg table(
        opp_agg varchar(5),
        col_agg varchar(100)
    );

    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(@table) AND type in (N'U'))
        Set @ind_tmpTbl=0;
    ELSE IF OBJECT_ID('tempdb..'+ltrim(rtrim(@table))) IS NOT NULL
        Set @ind_tmpTbl=1;

    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(@new_table) AND type in (N'U')) OR 
        OBJECT_ID('tempdb..'+ltrim(rtrim(@new_table))) IS NOT NULL
    Begin
        Set @query='DROP TABLE '+@new_table+'';
        Exec (@query);
    End;

    Select @query='Select distinct '+@column+' From '+(case when @ind_tmpTbl=1 then 'tempdb.' else '' end)+@schema+'.'+@table+' where '+@column+' is not null;';
    Print @query;

    Insert into #pvt_column(pivot_col)
    Exec (@query)

    While charindex(',',@agg,1)>0
    Begin
        Select @aggDet=Substring(@agg,1,charindex(',',@agg,1)-1);

        Insert Into @column_agg(opp_agg,col_agg)
        Values(substring(@aggDet,1,charindex('(',@aggDet,1)-1),ltrim(rtrim(replace(substring(@aggDet,charindex('[',@aggDet,1),charindex(']',@aggDet,1)-4),')',''))));

        Set @agg=Substring(@agg,charindex(',',@agg,1)+1,len(@agg))

    End

    Declare cur_agg cursor read_only forward_only local static for
    Select 
        opp_agg,col_agg
    from @column_agg;

    Open cur_agg;

    Fetch Next From cur_agg
    Into @opp_agg,@col_agg;

    While @@fetch_status=0
    Begin

        Declare cur_col cursor read_only forward_only local static for
        Select 
            pivot_col 
        From #pvt_column;

        Open cur_col;

        Fetch Next From cur_col
        Into @pivot_col;

        While @@fetch_status=0
        Begin

            Select @query_col_pvt='isnull('+@opp_agg+'(case when '+@column+'='+quotename(@pivot_col,char(39))+' then '+@col_agg+
            ' else null end),0) as ['+lower(Replace(Replace(@opp_agg+'_'+convert(varchar(100),@pivot_col)+'_'+replace(replace(@col_agg,'[',''),']',''),' ',''),'&',''))+
                (case when @add_to_col_name is null then space(0) else '_'+isnull(ltrim(rtrim(@add_to_col_name)),'') end)+']'
            print @query_col_pvt
            Select @full_query_pivot=@full_query_pivot+@query_col_pvt+', '

            --print @full_query_pivot

            Fetch Next From cur_col
            Into @pivot_col;        

        End     

        Close cur_col;
        Deallocate cur_col;

        Fetch Next From cur_agg
        Into @opp_agg,@col_agg; 
    End

    Close cur_agg;
    Deallocate cur_agg;

    Select @full_query_pivot=substring(@full_query_pivot,1,len(@full_query_pivot)-1);

    Select @query='Select '+@sel_cols+','+@full_query_pivot+' into '+@new_table+' From '+(case when @ind_tmpTbl=1 then 'tempdb.' else '' end)+
    @schema+'.'+@table+' Group by '+@sel_cols+';';

    print @query;
    Exec (@query);

End;
GO

Dies ist ein Beispiel für die Ausführung:

Exec dbo.rs_pivot_table @schema=dbo,@table=##TEMPORAL1,@column=tip_liq,@agg='sum([val_liq]),avg([can_liq]),',@sel_cols='cod_emp,cod_con,tip_liq',@new_table=##TEMPORAL1PVT;

dann Select * From ##TEMPORAL1PVTwürde zurückkehren:

Geben Sie hier die Bildbeschreibung ein

MelgoV
quelle
2
select * from (select name, ID from Empoyee) Visits
    pivot(sum(ID) for name
    in ([Emp1],
    [Emp2],
    [Emp3]
    ) ) as pivottable;
Muhammad Bilal
quelle
2

Hier ist eine Überarbeitung der obigen Antwort von @Tayrn, die Ihnen helfen könnte, das Schwenken ein wenig einfacher zu verstehen:

Dies ist vielleicht nicht der beste Weg, dies zu tun, aber dies hat mir geholfen, mich mit dem Schwenken von Tischen zu beschäftigen.

ID = Zeilen, die Sie schwenken möchten

MY_KEY = die Spalte, die Sie aus Ihrer ursprünglichen Tabelle auswählen und die die Spaltennamen enthält, die Sie schwenken möchten.

VAL = der Wert, den Sie unter jeder Spalte zurückgeben möchten.

MAX (VAL) => Kann durch andere Aggregatfunktionen ersetzt werden. SUMME (VAL), MIN (VAL), ETC ...

DECLARE @cols AS NVARCHAR(MAX),
@query  AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(MY_KEY) 
                from yt
                group by MY_KEY
                order by MY_KEY ASC
        FOR XML PATH(''), TYPE
        ).value('.', 'NVARCHAR(MAX)') 
    ,1,1,'')
set @query = 'SELECT ID,' + @cols + ' from 
         (
            select ID, MY_KEY, VAL 
            from yt
        ) x
        pivot 
        (
            sum(VAL)
            for MY_KEY in (' + @cols + ')
        ) p '

        execute(@query);
FarajDaoud
quelle
2

Geben Sie einfach eine Vorstellung davon, wie andere Datenbanken dieses Problem lösen. DolphinDBhat auch eingebaute Unterstützung für das Schwenken und das SQL sieht viel intuitiver und ordentlicher aus. Es ist so einfach wie das Angeben der Schlüsselspalte ( Store), der schwenkbaren Spalte ( Week) und der berechneten Metrik ( sum(xCount)).

//prepare a 10-million-row table
n=10000000
t=table(rand(100, n) + 1 as Store, rand(54, n) + 1 as Week, rand(100, n) + 1 as xCount)

//use pivot clause to generate a pivoted table pivot_t
pivot_t = select sum(xCount) from t pivot by Store, Week

DolphinDB ist eine spaltenweise Hochleistungsdatenbank. Die Berechnung in der Demo kostet auf einem Dell XPS-Laptop (i7-CPU) nur 546 ms. Weitere Informationen finden Sie im Online-DolphinDB-Handbuch https://www.dolphindb.com/help/index.html?pivotby.html

Davis Zhou
quelle