在SQL Server中使用“Pivot”将行转换为列

329

我已经阅读了有关MS数据透视表的资料,但仍然无法正确地使用它。

我创建了一个临时表,假设第一列是店铺编号,第二列是周数,最后一列是某种类型的总计。此外,周数是动态的,而店铺编号是静态的。

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

在侧面存储数字,在顶部存储周数。


可能是重复的吗?那个可能是重复的。看看日期! - Lynn
9个回答

400
如果您正在使用SQL Server 2005+,则可以使用PIVOT函数将数据从行转换为列。如果周数未知,则似乎需要使用动态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 请查看以下答案(https://dev59.com/zWUo5IYBdhLWcg3wkQNq#27532568)。我没有逐字尝试过,但这个概念是可行的。 - ruffin
1
+1 “听起来你需要使用动态SQL,如果周数未知,但最好最初使用硬编码版本查看正确的代码。”与Qlikview通用函数(https://community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic)不同,它允许不需要显式命名不同的“FOR ____ IN(...)” - Nate Anderson
1
如果您在使用CTE(公用表达式)构建数据透视表时,先前定义了cte3 AS (select ... ),并且在使用@cols@query的逻辑中出现错误,报错信息为“无效对象名称'cte3'”,那么该如何解决呢? - Elizabeth
3
太棒了 - 干得好@bluefeet。我以前从未使用过 STUFF(...) (或者也没有使用过 XML PATH )。为了让其他读者受益,所有这些都是将列名连接起来并去掉前导逗号。请注意,我认为以下内容略微简单:select @cols =(SELECT DISTINCT QUOTENAME(Week) + ',' from yt order by 1 FOR XML PATH('')) set @cols = SUBSTRING(@cols, 1, LEN(@cols) - 1)... 通过用distinct替换group byorder by 1,并手动去掉后缀的逗号! - DarthPablo
显示剩余3条评论

30

这是用于动态周数的。

完整示例在此处:SQL动态透视表

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

嘿,我有一个小问题,需要动态地旋转表格,你认为可以帮我解决吗?https://dbfiddle.uk/?rdbms=postgres_9.6&fiddle=de78ff10420c713d4ac4a444f9d5e69a - Silly Volley
@SillyVolley,这里有一个问题,你没有指定需要透视的内容。而且我不知道在Postgres中是否可以这么做,所以我在SQL Server中完成了这个任务:https://dbfiddle.uk/?rdbms=sqlserver_2012&fiddle=911ca0e83d8311b70c049b5036b866a6 - Enkode

17

我之前通过使用子查询实现了相同的事情。因此,如果您的原始表名为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
虽然这样做更容易,但它是一项非常昂贵的操作,这些子查询必须针对从表中返回的每一行执行一次。 - Greg

11

以下是您可以做的内容:

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

演示


7

我正在编写一个存储过程(SP),它可能对这个目的有用。基本上,这个SP可以透视任何表格并返回一个新的透视表格或者只返回数据集合。以下是它的执行方式:

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将返回以下内容:

enter image description here


5
以下是@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);

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

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是一种列式高性能数据库。在戴尔XPS笔记本电脑(i7 CPU)上,演示中的计算仅需546毫秒即可完成。要了解更多详细信息,请参阅在线DolphinDB手册 https://www.dolphindb.com/help/index.html?pivotby.html


0

Pivot是SQL操作符之一,用于将一列中的唯一数据转换为输出中的多列。这也意味着将行转换为列(旋转表)。让我们考虑这个表:

enter image description here

如果我想根据每个客户购买的产品类型(扬声器,玻璃,耳机)来筛选此数据,然后使用透视操作符。
Select CustmerName, Speaker, Glass, Headset  
from TblCustomer  
   Pivot  
    (  
     Sum(Price) for Product in ([Speaker],[Glass],[Headset])  
    ) as PivotTable 

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接