在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

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


Answers:


356

如果使用的是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请参阅下面的答案。还没有逐字尝试,但是这个概念很合理。
鲁芬2015年

1
+1“如果周数未知,这听起来像您将需要使用动态sql,但最初使用硬碟版本更容易看到正确的代码。” 与Qlikview通用函数(community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic)不同,该函数不需要您明确命名其他“ FOR ____ IN(...)”
红豌豆

1
如果您要使用cte早期构建数据透视表。cte3 AS (select ... )那么您就会在上面定义了带有@cols和的逻辑,@query这是一个错误。`无效的对象名称'cte3'。如何解决该问题。–
伊丽莎白

3
这太棒了-不错的@bluefeet。我以前从未用过STUFF(...)(或两者XML PATH皆有)。为了其他读者的利益,所有要做的就是加入列名并砍掉前导逗号。请注意,我认为以下操作稍微简单一些:select @cols =(SELECT DISTINCT QUOTENAME(Week)+','从yt开始按1 FOR XML PATH(''))set @cols = SUBSTRING(@cols,1,LEN( @cols) - 1)...更换group bydistinctorder by 1手动砍了一个后缀逗号!
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/...
傻排球

@SillyVolley这是一个,您没有指定要更改的内容。我也不知道您是否可以在Postgres中做到这一点,所以我在SQL Server中做到了:dbfiddle.uk/…–
Enkode,

16

通过使用子查询,我已经实现了相同的目的。因此,如果您的原始表名为StoreCountsByWeek,并且有一个单独的表列出了Store 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
这很容易,但是这是一个非常昂贵的操作,对于从表返回的每一行,这些子查询必须执行一次。
格雷格

11

您可以执行以下操作:

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

演示


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会返回:

在此处输入图片说明



2

这是上面@Tayrn答案的修订版,可以帮助您更轻松地进行透视:

这可能不是执行此操作的最佳方法,但这正是帮助我解决如何旋转表的原因。

ID =您要旋转的行

MY_KEY =您要从原始表中选择的列,该表包含要透视的列名。

VAL =您想在每列下返回的值。

MAX(VAL)=>可以用其他聚合函数替换。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

只是让您了解其他数据库如何解决此问题。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是一个柱状高性能数据库。在dell xps笔记本电脑(i7 cpu)上,演示中的计算成本低至546毫秒。要获取更多详细信息,请参阅在线DolphinDB手册https://www.dolphindb.com/help/index.html?pivotby.html

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.