Konwertuj wiersze na kolumny za pomocą „Pivot” w SQL Server

279

Przeczytałem rzeczy na temat tabel przestawnych MS i nadal mam problemy z uzyskaniem tego poprawnego.

Mam tabelę tymczasową, która jest tworzona, powiemy, że kolumna 1 to numer sklepu, a kolumna 2 to numer tygodnia, a na koniec kolumna 3 to w sumie jakiś typ. Również liczby tygodni są dynamiczne, numery sklepów są statyczne.

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

Chciałbym, aby wyszedł jako stół przestawny, taki jak ten:

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

Przechowuj numery z boku, a tygodnie u góry.

Lynn
źródło
1
możliwy duplikat dynamicznego zapytania PIVOT programu SQL Server?
RichardTheKiwi

Odpowiedzi:

356

Jeśli korzystasz z programu SQL Server 2005+, możesz użyć PIVOTfunkcji do przekształcenia danych z wierszy w kolumny.

Wygląda na to, że będziesz musiał użyć dynamicznego SQL, jeśli tygodnie są nieznane, ale łatwiej jest zobaczyć poprawny kod, używając początkowo wersji na stałe.

Po pierwsze, oto kilka szybkich definicji tabel i danych do użycia:

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);

Jeśli twoje wartości są znane, wówczas na stałe zakodujesz zapytanie:

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

Zobacz prezentację SQL

Jeśli chcesz dynamicznie wygenerować numer tygodnia, Twój kod będzie:

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);

Zobacz prezentację SQL .

Wersja dynamiczna generuje listę weekliczb, które należy przekonwertować na kolumny. Oba dają ten sam wynik:

| STORE |   1 |   2 |   3 |
---------------------------
|   101 | 138 | 282 | 220 |
|   102 |  96 | 212 | 123 |
|   105 |  37 |  78 |  60 |
|   109 |  59 |  97 |  87 |
Taryn
źródło
4
Bardzo dobrze! Ale jak wyeliminować kolumnę, gdy wszystkie wartości tej kolumny są równe NULL?
ZooZ,
1
@ZooZ Zobacz odpowiedź poniżej . Nie wypróbowałem tego dosłownie, ale koncepcja jest dobra.
ruffin
1
+1 „Wygląda na to, że będziesz musiał użyć dynamicznego SQL, jeśli tygodnie są nieznane, ale łatwiej jest zobaczyć prawidłowy kod na początku, używając wersji z twardą pamięcią podręczną”. W przeciwieństwie do funkcji Qlikview Generic ( community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic ), która pozwala, nie wymaga jawnego nadawania nazwy „FOR ____ IN (...)”
czerwony groszek
1
Jeśli budujesz wcześniej tabelę przestawną z cte. cte3 AS (select ... )wtedy masz zdefiniowaną powyżej logikę za pomocą @colsi @query... wystąpił błąd. Niepoprawna nazwa obiektu „cte3”. jak to naprawić. -
Elizabeth
3
To fantastyczne - fajne @bluefeet. Nigdy wcześniej nie korzystałem STUFF(...)(ani XML PATHjednego z nich). Z korzyścią dla innych czytelników wystarczy połączyć nazwy kolumn i odciąć przecinek wiodący. Uwaga: Myślę, że następujące czynności są nieco prostsze: wybierz @cols = (WYBIERZ NAZWĘ ODNIESIENIA (Tydzień) + ',' z zamówienia o 1 dla ŚCIEŻKI XML ('')) ustaw @cols = SUBSTRING (@cols, 1, LEN ( @cols) - 1) ... zastępując group byprzez distincta order by 1i ręczne siekanie z dodaną literą przecinek!
DarthPablo
26

Odbywa się to przez dynamiczną liczbę tygodni.

Pełny przykład tutaj: 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
źródło
Hej, mam skrzypce, w której muszę dynamicznie przestawiać tabele. Czy myślisz, że możesz mi w tym pomóc? dbfiddle.uk/…
Silly Volley
@SillyVolley tutaj jest jeden, nie określiłeś, co chcesz przestawiać. Nie wiem też, czy możesz to zrobić w Postgres, więc zrobiłem to w SQL Server: dbfiddle.uk/…
Enkode,
16

Osiągnąłem to samo wcześniej, używając podkwerend. Więc jeśli twoja oryginalna tabela nazywała się StoreCountsByWeek, a ty miałeś osobną tabelę, która zawierała identyfikatory sklepu, to wyglądałoby to tak:

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

Zaletą tej metody jest to, że składnia jest bardziej przejrzysta i ułatwia przyłączanie się do innych tabel, aby również przyciągać inne pola do wyników.

Moje anegdotyczne wyniki są takie, że uruchomienie tego zapytania w kilku tysiącach wierszy wykonanych w czasie krótszym niż jedna sekunda, a tak naprawdę miałem 7 podkwerend. Ale jak zauważono w komentarzach, zrobienie tego w ten sposób jest bardziej kosztowne obliczeniowo, więc należy zachować ostrożność przy stosowaniu tej metody, jeśli oczekuje się, że będzie działać na dużych ilościach danych.

Eric Barr
źródło
8
jest to łatwiejsze, ale jest to bardzo kosztowna operacja, te podzapytania muszą być wykonane raz dla każdego wiersza zwróconego z tabeli.
Greg
11

Oto, co możesz zrobić:

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

PRÓBNY

Praveen Nambiar
źródło
5

Piszę sp, który może być przydatny do tego celu, w zasadzie ten sp przestawia dowolną tabelę i zwraca nową tabelę przestawną lub zwraca tylko zestaw danych, oto sposób jej wykonania:

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;

pamiętaj, że w parametrze @agg nazwy kolumn muszą być z'[' a parametr musi kończyć się przecinkiem','

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

To jest przykład wykonania:

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;

wtedy Select * From ##TEMPORAL1PVTwróci:

wprowadź opis zdjęcia tutaj

MelgoV
źródło
2
select * from (select name, ID from Empoyee) Visits
    pivot(sum(ID) for name
    in ([Emp1],
    [Emp2],
    [Emp3]
    ) ) as pivottable;
Muhammad Bilal
źródło
2

Oto wersja powyższej odpowiedzi @Tayrn, która może ułatwić zrozumienie przestawiania:

To może nie być najlepszy sposób, aby to zrobić, ale to pomogło mi owinąć głowę, jak przestawić tabele.

ID = wiersze, które chcesz przestawić

MY_KEY = kolumna, którą wybierasz z oryginalnej tabeli, która zawiera nazwy kolumn, które chcesz przestawić.

VAL = wartość, którą chcesz zwrócić pod każdą kolumną.

MAX (VAL) => Można zastąpić innymi funkcjami agregującymi. SUMA (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
źródło
2

Po prostu daj ci pojęcie, jak inne bazy danych rozwiązują ten problem. DolphinDBma także wbudowaną obsługę przestawiania, a sql wygląda na bardziej intuicyjny i schludny. Jest to tak proste, jak określenie kolumny kluczowej ( Store), kolumny przestawnej ( Week) i obliczonej metryki ( 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 to kolumnowa, wysokowydajna baza danych. Obliczenia w wersji demo kosztują zaledwie 546 ms na laptopie Dell XPS (i7 CPU). Aby uzyskać więcej informacji, zapoznaj się z instrukcją online DolphinDB https://www.dolphindb.com/help/index.html?pivotby.html

Davis Zhou
źródło