Chuyển đổi hàng thành cột bằng 'Pivot' trong SQL Server


279

Tôi đã đọc nội dung trên bảng xoay vòng của MS và tôi vẫn gặp sự cố khi sửa lỗi này.

Tôi có một bảng tạm thời đang được tạo, chúng tôi sẽ nói rằng cột 1 là số Cửa hàng và cột 2 là số tuần và cuối cùng cột 3 là tổng số loại. Ngoài ra các số Tuần là động, số cửa hàng là tĩnh.

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

Tôi muốn nó xuất hiện dưới dạng bảng xoay vòng, như thế này:

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

Lưu trữ số bên cạnh và tuần trên đầu trang.


1
có thể trùng lặp truy vấn PIVOT động
RichardTheKiwi

Câu trả lời:


356

Nếu bạn đang sử dụng SQL Server 2005+, thì bạn có thể sử dụng PIVOThàm để chuyển đổi dữ liệu từ các hàng thành các cột.

Có vẻ như bạn sẽ cần phải sử dụng sql động nếu không biết các tuần nhưng dễ dàng hơn để xem mã chính xác bằng cách sử dụng phiên bản mã hóa cứng ban đầu.

Trước tiên, đây là một số định nghĩa và dữ liệu bảng nhanh để sử dụng:

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

Nếu các giá trị của bạn được biết đến, thì bạn sẽ mã hóa truy vấn:

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

Xem bản demo SQL

Sau đó, nếu bạn cần tạo số tuần một cách linh hoạt, mã của bạn sẽ là:

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

Xem bản demo SQL .

Phiên bản động, tạo danh sách các weeksố cần được chuyển đổi thành cột. Cả hai đều cho kết quả như nhau:

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

4
Rất đẹp! Nhưng làm thế nào để loại bỏ cột khi tất cả các giá trị của cột đó là NULL?
ZooZ

1
@ZooZ Xem câu trả lời dưới đây . Không thử nó nguyên văn, nhưng khái niệm là âm thanh.
ruffin

1
+1 "Có vẻ như bạn sẽ cần phải sử dụng sql động nếu không biết các tuần nhưng sẽ dễ dàng hơn để xem mã chính xác bằng cách sử dụng phiên bản được đệm cứng ban đầu." Không giống như chức năng Qlikview Generic ( Community.qlik.com/bloss/qlikviewdesignblog/2014/03/31/generic ) cho phép không yêu cầu bạn đặt tên rõ ràng là "FOR ____ IN (...)"
The Red Pea

1
Nếu bạn đang xây dựng một bảng trụ với một cte trước đó. cte3 AS (select ... )sau đó bạn có logic được xác định ở trên với @cols@query... có lỗi. 'Tên đối tượng không hợp lệ' cte3'.` làm thế nào để bạn sửa nó. -
Elizabeth

3
Điều này thật tuyệt vời - một @bluefeet đẹp. Tôi chưa từng sử dụng STUFF(...)trước đây (hoặc một XML PATHtrong hai). Vì lợi ích của những người đọc khác, tất cả những gì đang làm là tham gia các tên cột và cắt bỏ dấu phẩy hàng đầu. Lưu ý Tôi nghĩ cách sau đây đơn giản hơn một chút: select @cols = (CHỌN DISTINCT QUOTENAME (Tuần) + ',' từ thứ tự yt bằng 1 CHO XML PATH ('')) đặt @cols = SUBSTRING (@cols, 1, LEN ( @cols) - 1) ... thay thế group bybằng distinctorder by 1và tự chặt một hậu tố dấu phẩy!
DarthPablo

26

Đây là số động của tuần.

Ví dụ đầy đủ ở đây: 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

Này, tôi có một câu đố mà tôi cần phải xoay vòng các bảng một cách linh hoạt, bạn có nghĩ rằng bạn có thể giúp tôi với điều đó không? dbfiddle.uk/ từ
Volley

@SillyVcar ở đây là một, bạn không chỉ định những gì bạn muốn xoay vòng. Ngoài ra tôi không biết liệu bạn có thể làm điều này trong Postgres hay không vì vậy tôi đã làm điều đó trong SQL Server: dbfiddle.uk/ mẹo
Enkode

16

Tôi đã đạt được điều tương tự trước đây bằng cách sử dụng các truy vấn con. Vì vậy, nếu bảng ban đầu của bạn được gọi là StoreCountsByWeek và bạn có một bảng riêng liệt kê ID cửa hàng, thì nó sẽ trông như thế này:

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

Một lợi thế của phương pháp này là cú pháp rõ ràng hơn và nó cũng dễ dàng tham gia vào các bảng khác để kéo các trường khác vào kết quả.

Kết quả giai thoại của tôi là chạy truy vấn này trong vài nghìn hàng hoàn thành trong chưa đầy một giây và tôi thực sự có 7 truy vấn con. Nhưng như đã lưu ý trong các ý kiến, sẽ tốn kém hơn về mặt tính toán khi thực hiện theo cách này, vì vậy hãy cẩn thận khi sử dụng phương pháp này nếu bạn muốn nó chạy trên một lượng lớn dữ liệu.


8
nó dễ hơn, nhưng nó là một hoạt động rất tốn kém, những truy vấn con đó phải được thực hiện một lần cho mỗi hàng được trả về từ bảng.
Greg


5

Tôi đang viết một sp có thể hữu ích cho mục đích này, về cơ bản, sp này xoay vòng bất kỳ bảng nào và trả về một bảng mới được xoay vòng hoặc chỉ trả về tập dữ liệu, đây là cách để thực hiện nó:

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;

xin lưu ý rằng trong tham số @agg, các tên cột phải có '['và tham số phải kết thúc bằng dấu phẩy','

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

Đây là một ví dụ về thực thi:

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;

sau đó Select * From ##TEMPORAL1PVTsẽ trở lại:

nhập mô tả hình ảnh ở đây


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

2

Dưới đây là bản sửa đổi của câu trả lời @Tayrn ở trên có thể giúp bạn hiểu xoay vòng dễ dàng hơn một chút:

Đây có thể không phải là cách tốt nhất để làm điều này, nhưng đây là điều giúp tôi xoay quanh cách xoay vòng bảng.

ID = hàng bạn muốn xoay vòng

MY_KEY = cột bạn đang chọn từ bảng ban đầu chứa tên cột bạn muốn xoay.

VAL = giá trị bạn muốn trả về dưới mỗi cột.

MAX (VAL) => Có thể được thay thế bằng các hàm tổng hợp khác. SUM (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);

2

Chỉ cần cung cấp cho bạn một số ý tưởng làm thế nào các cơ sở dữ liệu khác giải quyết vấn đề này. DolphinDBcũng có hỗ trợ tích hợp cho xoay vòng và sql trông trực quan và gọn gàng hơn nhiều. Nó đơn giản như chỉ định cột khóa ( Store), cột xoay ( Week) và số liệu tính toán ( 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 là một cơ sở dữ liệu hiệu suất cao cột. Tính toán trong bản demo có giá thấp nhất là 546 ms trên máy tính xách tay dell xps (i7 cpu). Để biết thêm chi tiết, vui lòng tham khảo hướng dẫn sử dụng trực tuyến của DolphinDB https://www.dolphindb.com/help/index.html?pOLLby.html

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.