SQL Server'da 'Pivot'u kullanarak Satırları sütunlara dönüştürme


279

MS pivot tabloları şeyler okudum ve hala bu doğru alma sorunları yaşıyorum.

Oluşturulan bir geçici tablo var, biz sütun 1 bir mağaza numarası ve sütun 2 bir hafta sayı ve son olarak sütun 3 bazı tür toplam olduğunu söyleyeceğiz. Ayrıca Hafta numaraları dinamik, mağaza numaraları statiktir.

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

Bunun gibi bir pivot tablo olarak çıkmasını istiyorum:

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

Sayıları yan tarafta ve haftaları üstte saklayın.


Yanıtlar:


356

SQL Server 2005+ kullanıyorsanız PIVOT, verileri satırlardan sütunlara dönüştürmek için bu işlevi kullanabilirsiniz .

Haftalar bilinmiyorsa dinamik sql kullanmanız gerekecek gibi görünüyor, ancak başlangıçta sabit kodlu bir sürüm kullanarak doğru kodu görmek daha kolaydır.

İlk olarak, kullanım için bazı hızlı tablo tanımları ve veriler:

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

Değerleriniz biliniyorsa, sorguyu sabit olarak kodlayacaksınız:

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

SQL Demosuna bakın

Hafta numarasını dinamik olarak oluşturmanız gerekirse, kodunuz şöyle olacaktır:

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

Bkz. SQL Demosu .

Dinamik sürüm, weeksütunlara dönüştürülmesi gereken sayıların listesini oluşturur . Her ikisi de aynı sonucu verir:

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

4
Çok hoş! Ancak bu sütunun tüm değerleri NULL olduğunda sütunu nasıl kaldırabilirim?
ZooZ

1
@ZooZ Aşağıdaki cevaba bakınız . Kelimesi kelimesine denemedim, ama kavram sağlam.
ruffin

1
+1 "Haftalar bilinmiyorsa dinamik sql kullanmanız gerekecek gibi görünüyor, ancak başlangıçta sabit kodlu bir sürüm kullanarak doğru kodu görmek daha kolay." Qlikview Generic fonksiyonundan ( community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic ) farklı olarak, açıkça farklı "FOR ____ IN (...)" adını vermenizi gerektirmez
Kızıl Bezelye

1
Daha önce bir cte ile bir pivot tablo oluşturuyorsanız. cte3 AS (select ... )ve yukarıda tanımlanmış mantığınız @colsve @query... ile bir hata var. `` Geçersiz nesne adı 'cte3'.` bunu nasıl düzeltebilirsiniz. -
Elizabeth

3
Bu harika - güzel bir @bluefeet. Daha STUFF(...)önce hiç kullanmamıştım (ya da XML PATHikisini). Diğer okuyucuların yararı için, tek yapmanız gereken sütun adlarına katılmak ve önde gelen virgülün kesilmesidir. Not Aşağıdakilerin biraz daha basit olduğunu düşünüyorum: @cols = (SELECT QUOTENAME (Hafta) + ',' yt sırasından 1 FOR XML PATH ('') ile seçin @cols = SUBSTRING (@cols, 1, LEN ( @cols) - 1) ... değiştirilmesi group byyoluyla distinctve order by 1elle bir doğrama suffixed virgül!
DarthPablo

26

Bu, dinamik hafta sayısı içindir.

Burada tam örnek: 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

Hey, masaları dinamik olarak döndürmem gereken bir kemanım var, bana bu konuda yardımcı olabileceğini düşünüyor musun? dbfiddle.uk/…
Silly Volley

@SillyVolley burada, pivot etmek istediğiniz şeyi belirtmediniz. Ayrıca SQL Server'da yaptım böylece Postgres bunu yapabilirim bilmiyorum: dbfiddle.uk/…
Enkode 11:19

16

Aynı şeyi daha önce alt sorgular kullanarak başardım. Bu nedenle, orijinal tablonuz StoreCountsByWeek olarak adlandırılmışsa ve Mağaza Kimlikleri'ni listeleyen ayrı bir tablonuz olsaydı, şöyle görünür:

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

Bu yöntemin bir avantajı, sözdiziminin daha net olması ve diğer alanları da sonuçlara çekmek için diğer tablolara katılmayı kolaylaştırmasıdır.

Anekdot sonuçlarım, bu sorguyu bir saniyeden daha kısa sürede tamamlanmış birkaç bin satırda çalıştırıyor ve aslında 7 alt sorgum vardı. Ancak yorumlarda belirtildiği gibi, bu şekilde yapmak daha hesaplamalı olarak pahalıdır, bu nedenle büyük miktarda veri üzerinde çalışmasını bekliyorsanız bu yöntemi kullanmaya dikkat edin.


8
daha kolaydır, ancak çok pahalı bir işlemdir, bu alt sorguların tablodan döndürülen her satır için bir kez yürütülmesi gerekir.
Greg

11

Yapabilecekleriniz:

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

DEMO


5

Ben bu amaç için yararlı olabilir bir sp yazıyorum, temelde bu sp herhangi bir tablo pivot ve pivotlu yeni bir tablo döndürmek veya sadece veri kümesi döndürmek, bu yürütmek için yolu:

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;

@agg parametresinde sütun adlarının birlikte olması '['ve parametrenin virgülle bitmesi gerektiğini lütfen unutmayın','

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

Bu bir yürütme örneğidir:

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;

o Select * From ##TEMPORAL1PVTzaman dönecekti:

resim açıklamasını buraya girin


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

2

Aşağıda, @Tayrn cevabının, özetlemeyi biraz daha kolay anlamanıza yardımcı olabilecek bir revizyonu var:

Bu, bunu yapmanın en iyi yolu olmayabilir, ancak başımı tabloları nasıl döndüreceğime sarmamı sağlayan şey buydu.

ID = özetlemek istediğiniz satırlar

MY_KEY = orijinal tablonuzdan, döndürmek istediğiniz sütun adlarını içeren sütun.

VAL = her bir sütunun altına döndürmek istediğiniz değer.

MAX (VAL) => Diğer toplama işlevleriyle değiştirilebilir. TOPLA (VAL), MIN (VAL), VB ...

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

2

Sadece diğer veritabanlarının bu sorunu nasıl çözdüğü hakkında bir fikir verin. DolphinDBAyrıca pivotlama için yerleşik desteğe sahiptir ve sql çok daha sezgisel ve temiz görünür. Anahtar sütunu ( Store), döner sütun ( Week) ve hesaplanan metriği ( sum(xCount)) belirtmek kadar basittir .

//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 sütunlu bir yüksek performanslı veritabanıdır. Demodaki hesaplama, dell xps dizüstü bilgisayarda (i7 işlemci) 546 ms kadar düşük bir maliyete sahiptir. Daha fazla bilgi için lütfen çevrimiçi DolphinDB kılavuzuna bakın https://www.dolphindb.com/help/index.html?pivotby.html

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.