目次
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をマスターしてください!