Ubah Baris ke kolom menggunakan 'Pivot' di SQL Server

279

Saya telah membaca hal-hal di tabel MS pivot dan saya masih mengalami masalah untuk mendapatkan ini dengan benar.

Saya memiliki tabel temp yang sedang dibuat, kita akan mengatakan bahwa kolom 1 adalah nomor Toko, dan kolom 2 adalah angka minggu dan terakhir kolom 3 adalah total dari beberapa jenis. Juga angka Minggu dinamis, nomor toko statis.

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

Saya ingin ini keluar sebagai tabel pivot, seperti ini:

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

Menyimpan angka di samping dan minggu di bagian atas.

Lynn
sumber
1
kemungkinan duplikat kueri SQL Server dinamis PIVOT?
RichardTheKiwi

Jawaban:

356

Jika Anda menggunakan SQL Server 2005+, maka Anda dapat menggunakan PIVOTfungsi untuk mengubah data dari baris menjadi kolom.

Sepertinya Anda harus menggunakan sql dinamis jika minggu-minggu tidak diketahui tetapi lebih mudah untuk melihat kode yang benar menggunakan versi hard-kode pada awalnya.

Pertama, berikut adalah beberapa definisi tabel cepat dan data untuk digunakan:

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

Jika nilai-nilai Anda diketahui, maka Anda akan mengkode-keras kueri:

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

Lihat Demo SQL

Maka jika Anda perlu membuat angka minggu secara dinamis, kode Anda akan:

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

Lihat Demo SQL .

Versi dinamis, menghasilkan daftar weekangka yang harus dikonversi ke kolom. Keduanya memberikan hasil yang sama:

| STORE |   1 |   2 |   3 |
---------------------------
|   101 | 138 | 282 | 220 |
|   102 |  96 | 212 | 123 |
|   105 |  37 |  78 |  60 |
|   109 |  59 |  97 |  87 |
Taryn
sumber
4
Sangat bagus! Tetapi bagaimana cara menghilangkan kolom ketika semua nilai kolom itu NULL?
ZooZ
1
@ZooZ Lihat jawaban di bawah . Belum mencoba kata demi kata, tetapi konsepnya bagus.
ruffin
1
+1 "Sepertinya Anda perlu menggunakan sql dinamis jika minggu-minggu tidak diketahui tetapi lebih mudah untuk melihat kode yang benar menggunakan versi hard-cded pada awalnya." Berbeda dengan fungsi Generik Qlikview ( community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic ) yang memungkinkan tidak mengharuskan Anda secara eksplisit memberi nama "FOR ____ IN (...)"
The Red Pea
1
Jika Anda sedang membangun tabel pivot dengan cte sebelumnya. cte3 AS (select ... )maka Anda memiliki logika yang didefinisikan di atas dengan @colsdan @query... ada kesalahan.` Nama objek tidak valid 'cte3'.` bagaimana Anda memperbaikinya. -
Elizabeth
3
Ini fantastis - @bluefeet yang bagus. Saya tidak pernah menggunakan STUFF(...)sebelumnya (atau yang XML PATHbaik). Untuk kepentingan pembaca lain, semua yang dilakukan adalah bergabung dengan nama kolom dan memotong koma utama. Catatan saya pikir yang berikut ini sedikit lebih sederhana: pilih @cols = (PILIH PERPINDAHAN QUOTENAME (Minggu) + ',' dari urutan yt oleh 1 UNTUK XML PATH ('')) set @cols = SUBSTRING (@cols, 1, LEN ( @ cols) - 1) ... mengganti group byby distinctdan order by 1dan secara manual memotong koma yang suffiks !
DarthPablo
26

Ini untuk # minggu dinamis.

Contoh lengkap di sini: 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
sumber
Hei saya punya biola di mana saya perlu memutar tabel secara dinamis, apakah Anda pikir Anda dapat membantu saya dengan itu? dbfiddle.uk/…
Silly Volley
@SillyVolley di sini adalah satu, Anda tidak menentukan apa yang ingin Anda putar. Saya juga tidak tahu apakah Anda bisa melakukan ini di Postgres, jadi saya melakukannya di SQL Server: dbfiddle.uk/…
Enkode
16

Saya telah mencapai hal yang sama sebelumnya dengan menggunakan subqueries. Jadi jika tabel asli Anda disebut StoreCountsByWeek, dan Anda memiliki tabel terpisah yang mencantumkan ID Toko, maka akan terlihat seperti ini:

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

Salah satu keuntungan dari metode ini adalah bahwa sintaks lebih jelas dan membuatnya lebih mudah untuk bergabung ke tabel lain untuk menarik bidang lain ke dalam hasil juga.

Hasil anekdotal saya adalah menjalankan kueri ini selama beberapa ribu baris diselesaikan dalam waktu kurang dari satu detik, dan saya benar-benar memiliki 7 subquery. Tetapi seperti disebutkan dalam komentar, lebih mahal secara komputasional untuk melakukannya dengan cara ini, jadi berhati-hatilah dalam menggunakan metode ini jika Anda mengharapkannya berjalan pada sejumlah besar data.

Eric Barr
sumber
8
itu lebih mudah, tetapi ini adalah operasi yang sangat mahal, subqueries tersebut harus dijalankan sekali untuk setiap baris yang dikembalikan dari tabel.
Greg
11

Inilah yang dapat Anda lakukan:

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

DEMO

Praveen Nambiar
sumber
6

Saya sedang menulis sebuah sp yang dapat berguna untuk tujuan ini, pada dasarnya sp ini memutar semua tabel dan mengembalikan tabel baru yang diputar atau mengembalikan hanya set data, ini adalah cara untuk menjalankannya:

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;

harap dicatat bahwa dalam parameter @agg nama kolom harus bersama '['dan parameter harus diakhiri dengan koma','

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

Ini adalah contoh eksekusi:

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;

maka Select * From ##TEMPORAL1PVTakan kembali:

masukkan deskripsi gambar di sini

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

Berikut ini adalah revisi dari jawaban @Tayrn di atas yang mungkin membantu Anda memahami berputar sedikit lebih mudah:

Ini mungkin bukan cara terbaik untuk melakukan ini, tapi inilah yang membantu saya membungkus kepala saya di sekitar bagaimana cara memutar tabel.

ID = baris yang ingin Anda putar

MY_KEY = kolom yang Anda pilih dari tabel asli yang berisi nama kolom yang ingin Anda putar.

VAL = nilai yang ingin Anda kembalikan di bawah setiap kolom.

MAX (VAL) => Dapat diganti dengan fungsi agregat lainnya. SUM (VAL), MIN (VAL), DLL ...

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
sumber
2

Berikan Anda beberapa ide bagaimana database lain menyelesaikan masalah ini. DolphinDBjuga memiliki dukungan bawaan untuk pivoting dan sql terlihat jauh lebih intuitif dan rapi. Sederhana seperti menentukan kolom kunci ( Store), kolom berputar ( Week), dan metrik yang dihitung ( 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 adalah basis data kinerja tinggi berbentuk kolom. Perhitungan dalam biaya demo serendah 546 ms pada laptop xell dell (i7 cpu). Untuk mendapatkan detail lebih lanjut, silakan merujuk ke manual DolphinDB online https://www.dolphindb.com/help/index.html?pivotby.html

Davis Zhou
sumber