ピボットテーブルは行を列に変換します。グループごとに1行、別のフィールドの値ごとに1列(たとえば四半期ごとに1列)という形です。SQL では、SUM などの集計関数の中で CASE を使い、各行の単位となる項目でグループ化して作成します。例は PostgreSQL、MySQL、SQLite、SQL Server で動作します。
SQL でピボットテーブルを作る方法
出力する列ごとに集計を1つずつ書きます。その中の CASE がその列に属する行だけを通し、GROUP BY でグループごとに1行にまとめます。
SELECT
row_column,
SUM(CASE WHEN pivot_column = 'value 1' THEN amount ELSE 0 END) AS value_1,
SUM(CASE WHEN pivot_column = 'value 2' THEN amount ELSE 0 END) AS value_2
FROM table_name
GROUP BY row_column;
ここでは各製品が1行になり、四半期ごとの数量が横に並びます。
-- Input: sales
-- product | quarter | units
-- ---------+---------+------
-- Keyboard | Q1 | 120
-- Keyboard | Q2 | 95
-- Monitor | Q1 | 40
-- Monitor | Q2 | 55
-- Mouse | Q2 | 210
SELECT
product,
SUM(CASE WHEN quarter = 'Q1' THEN units ELSE 0 END) AS q1,
SUM(CASE WHEN quarter = 'Q2' THEN units ELSE 0 END) AS q2
FROM sales
GROUP BY product
ORDER BY product;
-- Output:
-- product | q1 | q2
-- ---------+-----+----
-- Keyboard | 120 | 95
-- Monitor | 40 | 55
-- Mouse | 0 | 210
押さえておくべきルール
-
列名はすべて自分で指定します。SQL はデータから列を作れないため、第3四半期を加えるには3行目の
CASEが必要です。値の一覧が頻繁に変わる場合は、アプリケーションコード側でクエリを組み立ててください。 -
ELSE 0はSUMとカウント専用です。存在しない組み合わせを NULL ではなく 0 にします。AVG、MIN、MAXではELSEを省いてください。そうしないと 0 が実際の値として扱われます。
-- Input: the sales table above
SELECT
product,
ROUND(AVG(CASE WHEN quarter = 'Q1' THEN units ELSE 0 END), 1) AS q1_else_0,
ROUND(AVG(CASE WHEN quarter = 'Q1' THEN units END), 1) AS q1_no_else
FROM sales
GROUP BY product
ORDER BY product;
-- Output:
-- product | q1_else_0 | q1_no_else
-- ---------+-----------+-----------
-- Keyboard | 60.0 | 120.0
-- Monitor | 20.0 | 40.0
-- Mouse | 0.0 | NULL
別の構文
PostgreSQL と SQLite の FILTER や、SQL Server の PIVOT 演算子を見かけることもあります。どちらも存在しない組み合わせは 0 ではなく NULL のままになります。
-- Input: the sales table above
-- PostgreSQL and SQLite
SELECT
product,
SUM(units) FILTER (WHERE quarter = 'Q1') AS q1,
SUM(units) FILTER (WHERE quarter = 'Q2') AS q2
FROM sales
GROUP BY product
ORDER BY product;
-- SQL Server
SELECT product, [Q1] AS q1, [Q2] AS q2
FROM (SELECT product, quarter, units FROM sales) AS src
PIVOT (SUM(units) FOR quarter IN ([Q1], [Q2])) AS p
ORDER BY product;
-- Output:
-- product | q1 | q2
-- ---------+------+----
-- Keyboard | 120 | 95
-- Monitor | 40 | 55
-- Mouse | NULL | 210
MySQL のコードでは、CASE 版を短く書いた SUM(IF(quarter = 'Q1', units, 0)) をよく見かけます。PostgreSQL の tablefunc 拡張には crosstab() 関数もあります。詳しくはPostgreSQL で crosstab() を使ってピボットテーブルを作るをご覧ください。
ピボットテーブルはいつ使うべき?
行の一覧を表形式にしたほうが読みやすいときにピボットします。よくある理由は次のとおりです。
1つの指標を横並びで比較する
比較したい値(日、四半期、地域など)を長い一覧に散らばらせず、1行の中に並べます。
列ごとに複数の指標を表示する
出力列はそれぞれ独立した集計なので、組み合わせて表示できます。デバイスごとのセッション数とページビュー、チャネルごとの注文数と売上などです。
カテゴリごとに行数を数える
合計ではなく件数を数えるには、一致する行ごとに 1 を足すか、COUNT(CASE WHEN ... THEN 1 END) を使います。COUNT は他の行が生む NULL を無視します。ステータス別のタスク数や優先度別のチケット数はこうして求めます。
キーと値の行を列に変換する
設定や製品属性は、名前と値ごとに1行で保存されることがよくあります。MAX(CASE ...) でピボットすると、項目ごとに1行、属性ごとに1列になります。MAX はテキストにも使え、各グループ内の唯一の値を取り出します。
SQL ピボットテーブルの実例
プラットフォーム別のアプリ平均評価
平均を求めるときは ELSE を省きます。そうすれば、あるプラットフォームでレビューのないアプリは誤解を招くスコアではなく NULL になります。
-- Input: reviews
-- app | platform | rating
-- ---------+----------+-------
-- Budgetly | iOS | 5
-- Budgetly | iOS | 4
-- Budgetly | Android | 3
-- FitTrack | Android | 4
-- FitTrack | Android | 5
-- In SQL Server, AVG of an integer column drops the decimals: use rating * 1.0
SELECT
app,
ROUND(AVG(CASE WHEN platform = 'iOS' THEN rating END), 1) AS ios,
ROUND(AVG(CASE WHEN platform = 'Android' THEN rating END), 1) AS android
FROM reviews
GROUP BY app
ORDER BY app;
-- Output:
-- app | ios | android
-- ---------+------+--------
-- Budgetly | 4.5 | 3.0
-- FitTrack | NULL | 4.5
地域ごとのチャネル別注文数と売上
チャネルごとに注文数と合計の2つの指標を出します。
-- Input: orders
-- id | region | channel | amount
-- ---+--------+---------+-------
-- 1 | North | web | 120.00
-- 2 | North | store | 80.00
-- 3 | North | web | 45.50
-- 4 | South | store | 210.00
-- 5 | South | store | 64.00
SELECT
region,
COUNT(CASE WHEN channel = 'web' THEN 1 END) AS web_orders,
SUM(CASE WHEN channel = 'web' THEN amount ELSE 0 END) AS web_sales,
COUNT(CASE WHEN channel = 'store' THEN 1 END) AS store_orders,
SUM(CASE WHEN channel = 'store' THEN amount ELSE 0 END) AS store_sales
FROM orders
GROUP BY region
ORDER BY region;
-- Output:
-- region | web_orders | web_sales | store_orders | store_sales
-- -------+------------+-----------+--------------+------------
-- North | 2 | 165.50 | 1 | 80.00
-- South | 0 | 0 | 2 | 274.00
担当者ごとの優先度別サポートチケット数
優先度ごとの件数と合計です。COUNT(*) は引き続きグループ内のすべての行を数えます。
-- Input: tickets
-- id | agent | priority
-- ---+-------+---------
-- 1 | Priya | urgent
-- 2 | Priya | normal
-- 3 | Marco | normal
-- 4 | Priya | normal
-- 5 | Marco | low
SELECT
agent,
COUNT(CASE WHEN priority = 'low' THEN 1 END) AS low,
COUNT(CASE WHEN priority = 'normal' THEN 1 END) AS normal,
COUNT(CASE WHEN priority = 'urgent' THEN 1 END) AS urgent,
COUNT(*) AS total
FROM tickets
GROUP BY agent
ORDER BY agent;
-- Output:
-- agent | low | normal | urgent | total
-- ------+-----+--------+--------+------
-- Marco | 1 | 1 | 0 | 2
-- Priya | 0 | 2 | 1 | 3
ユーザーごとに1行の設定
各設定は名前と値で保存されています。設定していないユーザーはその列が NULL になります。
-- Input: user_settings
-- user_id | setting | value
-- --------+----------+--------------
-- 1 | theme | dark
-- 1 | language | en
-- 2 | language | de
-- 2 | timezone | Europe/Berlin
-- 3 | theme | light
SELECT
user_id,
MAX(CASE WHEN setting = 'theme' THEN value END) AS theme,
MAX(CASE WHEN setting = 'language' THEN value END) AS language,
MAX(CASE WHEN setting = 'timezone' THEN value END) AS timezone
FROM user_settings
GROUP BY user_id
ORDER BY user_id;
-- Output:
-- user_id | theme | language | timezone
-- --------+-------+----------+--------------
-- 1 | dark | en | NULL
-- 2 | NULL | de | Europe/Berlin
-- 3 | light | NULL | NULL
ピボットテーブルについてさらに学ぶ
このブログの記事:
-
PostgreSQL でピボットする方法: PostgreSQL での
CASEとcrosstab() -
PostgreSQL で crosstab() を使ってピボットテーブルを作る:
tablefunc拡張の詳細 - SQL CASE 文ガイド: すべてのピボット列の中にある条件
- SQL GROUP BY ガイド: ピボット前の行のグループ化
公式ドキュメント:
Beekeeper Studioは無料のオープンソースのデータベースGUIです
これまで使った中で最高のSQLクエリ&エディタツールです。データベースの管理に必要なものがすべて揃っています。 - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studioは高速で直感的、そして使いやすいツールです。多数のデータベースに対応し、Windows、Mac、Linuxで快適に動作します。
Beekeeper Studio 利用者の声
「Beekeeper Studio で以前の SQL ワークフローを完全に置き換えました。高速で直感的、データベース作業がまた楽しくなりました。」
「多くのデータベース GUI を試しましたが、Beekeeper は機能とシンプルさのバランスが完璧です。とにかくちゃんと動きます。」