แปลงแถวเป็นคอลัมน์โดยใช้ 'Pivot' ใน SQL Server


279

ฉันได้อ่านสิ่งต่าง ๆ บนตารางเดือย MS และฉันยังคงมีปัญหาในการแก้ไขให้ถูกต้อง

ฉันมีตารางชั่วคราวที่ถูกสร้างขึ้นเราจะบอกว่าคอลัมน์ 1 คือหมายเลขร้านค้าและคอลัมน์ 2 เป็นหมายเลขสัปดาห์และคอลัมน์สุดท้าย 3 คือจำนวนประเภททั้งหมด นอกจากนี้หมายเลขสัปดาห์ยังเป็นแบบไดนามิกหมายเลขร้านค้าเป็นแบบคงที่

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

ฉันต้องการให้มันออกมาเป็นตารางเดือยเช่นนี้

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

จัดเก็บตัวเลขลงด้านข้างและสัปดาห์ที่ด้านบน


คำตอบ:


356

หากคุณใช้ SQL Server 2005+ คุณสามารถใช้PIVOTฟังก์ชันเพื่อแปลงข้อมูลจากแถวเป็นคอลัมน์

ดูเหมือนว่าคุณจะต้องใช้ dynamic sql หากไม่ทราบสัปดาห์ แต่ง่ายกว่าที่จะเห็นรหัสที่ถูกต้องโดยใช้เวอร์ชันที่เขียนโค้ดยากในตอนแรก

ก่อนอื่นต่อไปนี้เป็นคำจำกัดความของตารางแบบย่อและข้อมูลสำหรับการใช้งาน:

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

หากค่าของคุณเป็นที่รู้จักกันแล้วคุณจะยากรหัสแบบสอบถาม:

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

ดูการสาธิต SQL

จากนั้นหากคุณต้องการสร้างหมายเลขสัปดาห์แบบไดนามิกรหัสของคุณจะเป็น:

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

ดูSQL สาธิต

เวอร์ชันไดนามิกสร้างรายการweekหมายเลขที่ควรแปลงเป็นคอลัมน์ ทั้งคู่ให้ผลลัพธ์เหมือนกัน:

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

4
ดีมาก! แต่จะกำจัดคอลัมน์ได้อย่างไรเมื่อค่าทั้งหมดของคอลัมน์นั้นเป็น NULL
ZooZ

1
@ZooZ ดูคำตอบด้านล่าง ยังไม่ได้ลองทุกคำ แต่แนวคิดคือเสียง
ruffin

1
+1 "ดูเหมือนคุณจะต้องใช้ไดนามิก sql หากไม่ทราบสัปดาห์ แต่มันจะง่ายกว่าที่จะเห็นรหัสที่ถูกต้องโดยใช้เวอร์ชันฮาร์ด - ซีดีในขั้นต้น" ไม่เหมือนกับฟังก์ชันทั่วไปของ Qlikview ( community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic ) ซึ่งอนุญาตให้คุณไม่ต้องตั้งชื่อ "FOR ____ IN (... )" อย่างชัดเจน
ถั่วแดง

1
หากคุณกำลังสร้างตารางสาระสำคัญด้วย cte ก่อนหน้านี้ cte3 AS (select ... )จากนั้นคุณมีตรรกะที่กำหนดไว้ข้างต้นด้วย@colsและ@query... มีข้อผิดพลาด "ชื่อวัตถุไม่ถูกต้อง 'cte3'" คุณจะแก้ไขได้อย่างไร -
Elizabeth

3
นี่เป็นสิ่งที่ยอดเยี่ยม - nice one @bluefeet ฉันไม่เคยใช้มาSTUFF(...)ก่อน (หรือXML PATHอย่างใดอย่างหนึ่ง) เพื่อประโยชน์ของผู้อ่านคนอื่น ๆ สิ่งที่ทำอยู่ก็คือการเข้าร่วมชื่อคอลัมน์และตัดเครื่องหมายจุลภาคชั้นนำ หมายเหตุฉันคิดว่าสิ่งต่อไปนี้ง่ายกว่าเล็กน้อย: select @cols = (เลือก DISTINCT QUOTENAME (สัปดาห์) + ',' จากคำสั่ง yt โดย 1 สำหรับเส้นทาง XML ('')) ตั้ง @cols = SUBSTRING (@cols, 1, LEN ( @cols) - 1) ... แทนที่group byโดยdistinctและorder by 1ด้วยตนเองและสับsuffixedจุลภาค!
DarthPablo

26

นี่เป็น # แบบไดนามิกของสัปดาห์

ตัวอย่างเต็มรูปแบบที่นี่: 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

เฮ้ฉันมีซอที่ฉันต้องหมุนตารางคุณคิดว่าคุณสามารถช่วยฉันได้ไหม? dbfiddle.uk/…
Silly Volley

@SillyVolley ที่นี่เป็นหนึ่งคุณไม่ได้ระบุสิ่งที่คุณต้องการจะหมุน นอกจากนี้ฉันไม่ทราบว่าคุณสามารถทำสิ่งนี้ใน Postgres ได้หรือไม่ฉันทำได้ใน SQL Server: dbfiddle.uk/…
Enkode

16

ฉันเคยทำสิ่งเดียวกันมาก่อนโดยใช้แบบสอบถามย่อย ดังนั้นหากตารางดั้งเดิมของคุณถูกเรียกว่า StoreCountsByWeek และคุณมีตารางแยกต่างหากที่ระบุ ID ร้านค้าดังนั้นมันจะมีลักษณะดังนี้:

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

ข้อดีอย่างหนึ่งของวิธีนี้คือไวยากรณ์มีความชัดเจนมากขึ้นและทำให้ง่ายต่อการเข้าร่วมกับตารางอื่น ๆ เพื่อดึงเขตข้อมูลอื่นลงในผลลัพธ์เช่นกัน

ผลลัพธ์โดยสรุปของฉันคือการเรียกใช้คิวรีนี้สองสามพันแถวเสร็จสมบูรณ์ในเวลาน้อยกว่าหนึ่งวินาทีและจริง ๆ แล้วฉันมี 7 แบบสอบถามย่อย แต่ตามที่ระบุไว้ในความคิดเห็นมันมีราคาแพงกว่าการคำนวณด้วยวิธีนี้ดังนั้นโปรดระมัดระวังเกี่ยวกับการใช้วิธีนี้ถ้าคุณคาดว่ามันจะทำงานบนข้อมูลจำนวนมาก


8
มันง่ายกว่า แต่มันเป็นการดำเนินการที่แพงมากแบบสอบถามย่อยเหล่านั้นจะต้องถูกเรียกใช้งานหนึ่งครั้งสำหรับแต่ละแถวที่ส่งคืนจากตาราง
เกร็ก


5

ฉันกำลังเขียน sp ที่อาจเป็นประโยชน์สำหรับวัตถุประสงค์นี้โดยทั่วไป sp pivot ใด ๆ ในตารางและส่งกลับตารางใหม่ pivoted หรือคืนเพียงชุดข้อมูลนี้เป็นวิธีการรัน:

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 ชื่อคอลัมน์จะต้องอยู่กับ'['และพารามิเตอร์จะต้องลงท้ายด้วยเครื่องหมายจุลภาค','

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

นี่คือตัวอย่างของการดำเนินการ:

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;

จากนั้นSelect * From ##TEMPORAL1PVTจะส่งคืน:

ป้อนคำอธิบายรูปภาพที่นี่



2

นี่คือการแก้ไขคำตอบ @Tayrn ด้านบนที่อาจช่วยให้คุณเข้าใจการหมุนได้ง่ายขึ้นเล็กน้อย:

นี่อาจไม่ใช่วิธีที่ดีที่สุดในการทำสิ่งนี้ แต่นี่คือสิ่งที่ช่วยฉันห่อหัวของฉันเกี่ยวกับวิธีการหมุนตาราง

ID = แถวที่คุณต้องการหมุน

MY_KEY = คอลัมน์ที่คุณเลือกจากตารางดั้งเดิมที่มีชื่อคอลัมน์ที่คุณต้องการหมุน

VAL = ค่าที่คุณต้องการกลับมาในแต่ละคอลัมน์

MAX (VAL) => สามารถถูกแทนที่ด้วยฟังก์ชันการรวมอื่น ๆ SUM (VAL), MIN (VAL), ฯลฯ ...

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

เพียงแค่ให้คุณทราบว่าฐานข้อมูลอื่นแก้ปัญหานี้ DolphinDBนอกจากนี้ยังมีการสนับสนุนในตัวสำหรับการหมุนเหวี่ยงและ sql ดูง่ายขึ้นและเป็นระเบียบมากขึ้น มันง่ายเหมือนการระบุคอลัมน์คีย์ ( Store), คอลัมน์หมุนเหวี่ยง ( Week) และเมตริกที่คำนวณได้ ( 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 เป็นฐานข้อมูลประสิทธิภาพสูงแบบเสา การคำนวณในการสาธิตมีค่าใช้จ่ายต่ำเพียง 546 มิลลิวินาทีบนแล็ปท็อป dell xps (i7 cpu) เพื่อรับรายละเอียดเพิ่มเติมโปรดดูคู่มือ DolphinDB ออนไลน์https://www.dolphindb.com/help/index.html?pivotby.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.