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関数を使用してデータを行から列に変換できます。

週が不明な場合は動的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 Demoを参照してください。

動的バージョンは、week列に変換する必要がある数値のリストを生成します。どちらも同じ結果になります:

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

4
非常に素晴らしい!しかし、その列のすべての値がNULLのときに列を削除するにはどうすればよいですか?
ZooZ、2015年

1
@ZooZ下記の回答を参照してください。完全に試したことはありませんが、コンセプトはしっかりしています。
ルフィン、2015年

1
+1「週が不明な場合は動的SQLを使用する必要があるようですが、最初にハードCDバージョンを使用して正しいコードを確認する方が簡単です。」Qlikview Generic関数(community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic)とは異なり、明示的に別の "FOR ____ IN(...)"に名前を付ける必要はありません
Red Pea

1
以前にcteでピボットテーブルを作成している場合。cte3 AS (select ... )そして、あなたがロジック上で定義されている@cols@query...あなたはそれを解決行う方法error.`無効なオブジェクト名」cte3'.`があります。–
エリザベス

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)...交換するgroup byことにより、distinctおよびorder by 1、手動でチョッピング接尾辞カンマを!
DarthPablo

26

これは、動的な週数です。

ここでの完全な例: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

テーブルを動的にピボットする必要があるフィドルがあるので、それを手伝ってくれると思いますか?dbfiddle.uk/...
愚かなボレー

ここの@SillyVolleyは1つです、あなたがピボットしたいものを指定しませんでした。また、私は、私はSQL Serverでそれをやったので、あなたはPostgresのでこれを行うことができるかどうかわからない: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

この方法の利点の1つは、構文がより明確になり、他のテーブルに結合して他のフィールドを結果に取り込むのが容易になることです。

私の事例的な結果は、このクエリを数千行以上実行すると1秒未満で完了し、実際には7つのサブクエリがあったことです。ただし、コメントで述べたように、この方法を使用すると計算コストが高くなるため、大量のデータで実行することが予想される場合は、このメソッドの使用に注意してください。


8
簡単ですが、これは非常に負荷の高い操作です。これらのサブクエリは、テーブルから返される行ごとに1回実行する必要があります。
グレッグ、


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は、円柱状の高性能データベースです。デモの計算コストは​​、デルのxpsラップトップ(i7 cpu)で546ミリ秒ほどです。詳細については、オンラインのDolphinDBマニュアルを参照してくださいhttps://www.dolphindb.com/help/index.html?pivotby.html

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.