【问题标题】:Convert Rows to columns using 'Pivot' in SQL Server在 SQL Server 中使用 \'Pivot\' 将行转换为列
【发布时间】:2023-01-25 02:21:12
【问题描述】:

我已经阅读了 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

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

【问题讨论】:

标签: sql sql-server pivot pivot-table


【解决方案1】:

如果您使用的是 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 Demo

然后,如果您需要动态生成周数,您的代码将是:

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 Demo。

动态版本生成应转换为列的 week 数字列表。两者给出相同的结果:

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

【讨论】:

  • 非常好!但是当该列的所有值都为 NULL 时如何删除该列?
  • @ZooZ 参见answer below。还没有逐字尝试过,但这个概念是合理的。
  • +1 “如果周数未知,听起来您将需要使用动态 sql,但最初使用硬盘版本更容易看到正确的代码。”与 Qlikview 通用函数 (community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic) 不同,它不需要您明确命名不同的“FOR ____ IN (...)”
  • 如果您之前正在使用 cte 构建数据透视表。cte3 AS (select ... ) 那么您将使用 @cols 和 @query 定义上述逻辑 ... 出现错误。`无效的对象名称'cte3'。`怎么办你解决这个问题。 –
  • 这太棒了 - 不错的@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和order by 1替换group by并手动切碎后缀逗号!
【解决方案2】:

这是动态的周数。

完整示例在这里: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/…
  • @SillyVolley 是其中之一,您没有指定要以什么为中心。另外我不知道你是否可以在 Postgres 中做到这一点,所以我在 SQL Server 中做到了:dbfiddle.uk/…
【解决方案3】:

我之前通过使用子查询实现了同样的事情。因此,如果您的原始表名为 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 个子查询。但是正如在 cmets 中指出的那样,这样做的计算成本更高,因此如果您希望它在大量数据上运行,请谨慎使用此方法。

【讨论】:

  • 它更容易,但这是一个非常昂贵的操作,必须为从表返回的每一行执行一次这些子查询。
【解决方案4】:

这是你可以做的:

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

DEMO

【讨论】:

    【解决方案5】:

    我正在编写一个可用于此目的的 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 会返回:

    【讨论】:

      【解决方案6】:

      这是对上面@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);
      

      【讨论】:

        【解决方案7】:
        select * from (select name, ID from Empoyee) Visits
            pivot(sum(ID) for name
            in ([Emp1],
            [Emp2],
            [Emp3]
            ) ) as pivottable;
        

        【讨论】:

          【解决方案8】:

          只是让您了解其他数据库如何解决此问题。 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 手册@​​987654321@

          【讨论】:

            【解决方案9】:

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

            如果我想根据每个客户的产品类型(扬声器、眼镜、耳机)过滤此数据,请使用 Pivot 运算符。

            Select CustmerName, Speaker, Glass, Headset  
            from TblCustomer  
               Pivot  
                (  
                 Sum(Price) for Product in ([Speaker],[Glass],[Headset])  
                ) as PivotTable 
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-05-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多