目次
PIVOTとは

PIVOTとは、テーブル値式の1つの列にある一意の値を取得し、出力内の複数の列に変換することにより、テーブル値式を行列変換する関係演算子です。
少し複雑な説明になってしまいましたが、簡単に説明すると「行を列に変換」してくれます。
PIVOTで集計が実行されるのは、最終出力で必要な残りの任意の列値に対して集計が必要な場合です。
実際にPIVOTの使い方を見て確認していきましょう。
PIVOTの使い方

PIVOTの基本的な使い方・構文は次のとおりです。
SELECT
    PIVOT列の値1,
    PIVOT列の値2,
    PIVOT列の値3,
    ・・・
FROM テーブル名
PIVOT (
    集計関数(集計対象列)
    FOR PIVOT列
    IN (PIVOT列の値1, PIVOT列の値2, PIVOT列の値3, ・・・)
) AS PIVOTテーブルの別名
サンプルで動きを確認してみましょう。
my_tableには、次のデータがあると仮定します。
| name | score | 
|---|---|
| s1 | 126 | 
| s2 | 220 | 
| s3 | 60 | 
| s2 | 500 | 
| s1 | 270 | 
| s1 | 388 | 
| s3 | 200 | 
nameとscoreの2列からなるmy_tableテーブルをもとに、PIVOTでnameごとに列を作成し、scoreの平均値を集計しましょう。
PIVOTは次のように記述します。
SELECT
    s1,
    s2,
    s3
FROM my_table
PIVOT (AVG(score) FOR name IN (s1, s2, s3)) AS pivot_table
         s1          s2          s3      
----------- ----------- -----------
       238          470         130
IN句で指定したnameが列になり、scoreの平均値が値となったpivot_tableができました。
UNPIVOTの使い方
UNPIVOTは、PIVOTの逆でテーブル値式の列を行に変換します。
こちらも使い方を見ていきましょう。
SELECT
    UNPIVOT列をまとめる列,
    UNPIVOT列の値が入る列
FROM テーブル名
UNPIVOT (
    UNPIVOT列の値が入る列
    FOR UNPIVOT列をまとめる列
    IN (UNPIVOT列の値1, UNPIVOT列の値2, UNPIVOT列の値3 ・・・)
) AS UNPIVOTテーブルの別名
サンプルで動きを確認してみましょう。
my_tableテーブルに、s1・s2・s3の3列を用意します。
そして、s1・s2・s3をname列に、各列の値をscore列に集計します。
SELECT
    name,
    score
FROM my_table
UNPIVOT (score FOR name IN (s1, s2, s3)) AS unpivot_table;
name              score   
----------- -----------
s1	            126
s2	            220
s3	             60
s2	            500
s1	            270
s1	            388
s3	            200
IN句に指定したs1〜s3列をname列の値に変換し、IN句に指定したそれぞれの列の値がscore列の値となっているのがわかります。
PIVOTで合計を求める

PIVOTを使って列ごとの合計を求めてみましょう。
合計を求める場合は「SUM」を使います。
先ほどの「PIVOTの使い方」で使用したテーブルで確認してみましょう。
SELECT
    s1,
    s2,
    s3
FROM my_table
PIVOT (SUM(score) FOR name IN (s1, s2, s3)) AS pivot_table;
         s1          s2          s3      
----------- ----------- -----------
       784          720         260
列ごとの合計を表示できるはずです。
PIVOTで動的SQLを活用する

行列の変換ができるPIVOT、UNPIVOTは非常に便利な機能ですが、このままだとIN句の指定は静的なものだけに限られてしまいます。
例えば、先ほどのテーブルのnameに「s4」のレコードを追加しても、s4のレコードは取得できません。
新しいnameが追加されるごとに指定を追加してもよいのですが、SQLを修正する必要があるので少し不便です。
そのため、レコードの追加に合わせて動的に指定を変更したい場合は「動的SQL」を活用しましょう。
動的SQLを使うと、nameの種類が増えても修正する必要がありません。
PIVOTの使い方で使用したSQLを、動的SQLに変更する場合は次のようになります。
DECLARE @score_list varchar(max) 
DECLARE @sql nvarchar(max)
SET @score_list = null
SELECT
    @score_list =
        CASE
            WHEN @score_list IS NULL THEN '[' + name + ']'
            ELSE @score_list + ', [' + name + ']'
        END
FROM my_table
GROUP BY name
SET @sql =
    'SELECT * FROM my_table PIVOT (AVG(score) FOR name in (' +
    @score_list +
    ')) AS pivot_table'
 
EXEC sp_executesql @sql;
@score_listを作成するSELECT文で、my_tableテーブルにあるnameをカンマ区切りでつなげています。
そして、SQL文のIN句に商品名@score_listを指定し、実行すれば動的にnameとその値のレコードを取得できます。
MySQLでpivotを実現
MySQLではPIVOTを使用することができません。
そのためPIVOT(行列変換)を実現するには、SQLを工夫する必要があります。
ここでは、次のデータを使って行列変換してみましょう。
| id | type_id | type_name | value | 
|---|---|---|---|
| 1 | 1 | name | Satou | 
| 1 | 2 | age | 25 | 
| 1 | 3 | sex | Male | 
| 2 | 1 | name | Takahashi | 
| 2 | 2 | age | 33 | 
| 2 | 3 | sex | Female | 
| 3 | 1 | name | Abe | 
| 3 | 2 | age | 29 | 
| 3 | 3 | sex | Male | 
SELECT id, 
    (SELECT value FROM my_table WHERE type_name = 'name' AND id = m.id) AS name,         
    (SELECT value FROM my_table WHERE type_name = 'age' AND id = m.id) AS age, 
    (SELECT value FROM my_table WHERE type_name = 'sex' AND id = m.id) AS sex FROM my_table m 
GROUP BY id;
         id        name         age         sex
----------- ----------- ----------- -----------
          1       Satou          25        Male
          2   Takahashi          33      FeMale
          3         Abe          29        Male
SELECTの部分でname、age、sexごとに列を作成し、idごとにGROUP BYで並べます。
PIVOTで複数列変換する

PIVOTで複数列を行列変換する際は、INの中の指定でASによる別名をつけて指定しましょう。
変換するのは次のデータです。
| id | score | time | 
|---|---|---|
| s1 | 126 | 60 | 
| s2 | 220 | 100 | 
| s3 | 60 | 25 | 
| s2 | 500 | 260 | 
| s1 | 270 | 120 | 
| s1 | 388 | 175 | 
| s3 | 200 | 140 | 
SELECT 
     pv.S1_score,
     pv.S1_time,
     pv.S2_score,
     pv.S2_time,
     pv.S3_score,
     pv.S3_time
FROM (
    SELECT id,
           SUM(score) AS score,
           SUM(time) AS time
    FROM   my_table
    GROUP BY id
)
PIVOT (
    SUM(score) AS score,
    SUM(time) AS time
    FOR コード IN (
        's1' AS S1,
        's2' AS S2,
        's3' AS S3
    )
) pv
      S1_score          S1_time       S2_score         S2_time        S3_score         S3_time
-------------- ---------------- -------------- --------------- --------------- ---------------
           784              355            720             360             260             165 
まとめ

SQLのPIVOTの使い方を解説しました。
PIVOTは行列変換ができる便利な機能です。
SQLは少し複雑になりますが、ぜひこの記事を参考にPIVOTをマスターしてください!