SQLでデータを集計する際、GROUP BY
句は非常に強力な武器です。しかし、「部署ごとの売上」「役職ごとの売上」「全社の総売上」といった複数の集計を一度に行いたい場合、どうしていますか? おそらく、それぞれの集計クエリを書いて UNION ALL
で結合する、といった面倒な手順を踏んでいるのではないでしょうか。この方法ではSQLが複雑で長くなり、メンテナンス性も低下してしまいます。
実は、SQLにはこうした課題をスマートに解決するための強力な機能が備わっています。それが、GROUPING SETS
、ROLLUP
、CUBE
といった拡張GROUP BY
句です。これらは「グループ化セット」とも呼ばれ、1つのクエリで複数パターンの集計を一度に取得できるため、データ分析やレポーティングの効率を劇的に向上させます。
この記事では、GROUPING SETS
、ROLLUP
、CUBE
の基本的な使い方から、それぞれの違い、そしてどのような場面で役立つのかまでを、具体的なサンプルデータとコード、図解を交えながら網羅的に解説します。データベーススペシャリスト試験などの資格対策はもちろん、実務でのデータ活用スキルを一段階レベルアップさせたい方も、ぜひ最後までご覧ください。
目次
まずは基本から│GROUP BY句の課題とサンプルデータ
GROUPING SETS
などの高度な機能を見ていく前に、まずは基本となるGROUP BY
句の役割と、複数の集計を行おうとした際の課題について簡単におさらいしましょう。
GROUP BY
句と複数集計の課題
GROUP BY
は、指定した列(例:部署名)の値が同じ行を一つのグループにまとめ、SUM()
やCOUNT()
といった集計関数を適用するための句です。しかし、例えば「①部署ごとの合計給与」と「②役職ごとの合計給与」を同時に見たい場合、一つのGROUP BY
句でこれを表現することはできません。
従来の方法では、以下のようにそれぞれの集計クエリを書き、UNION ALL
で結合する必要がありました。
【UNION ALL
を使った複数パターンの集計例】
-- ① 部署ごとの合計給与
SELECT
department,
NULL AS position,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
department
UNION ALL
-- ② 役職ごとの合計給与
SELECT
NULL AS department,
position,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
position;
この方法でも結果は得られますが、クエリが冗長になり、集計パターンが増えるほどSQLは複雑化していきます。この課題を解決するのが、これから学ぶグループ化セットです。
今回使用するサンプルデータ
この記事では、以下のような「従業員(employees)」テーブルを例に解説を進めます。部署(department)、役職(position)、性別(gender)、給与(salary)といったカラムを持つシンプルなテーブルです。
【employees テーブル】
id | department | position | gender | salary |
---|---|---|---|---|
1 | 営業部 | 部長 | 男性 | 800 |
2 | 営業部 | 課長 | 女性 | 600 |
3 | 営業部 | 一般 | 男性 | 400 |
4 | 営業部 | 一般 | 女性 | 400 |
5 | 開発部 | 部長 | 男性 | 850 |
6 | 開発部 | 課長 | 男性 | 650 |
7 | 開発部 | 一般 | 女性 | 450 |
8 | 人事部 | 課長 | 女性 | 580 |
9 | 人事部 | 一般 | 男性 | 380 |
このデータを元に、GROUPING SETS
、ROLLUP
、CUBE
がどのように動作するのかを見ていきましょう。
GROUPING SETS句のSQL│指定した組み合わせを一度に集計
UNION ALL
の冗長さを解消する最初の切り札がGROUPING SETS
です。これは、集計したいグループの組み合わせを括弧 ()
で自由に指定できる機能です。まるでレストランでアラカルトメニューを注文するように、欲しい集計パターンだけをピンポイントで要求できます。
基本的な使い方とSQL構文
GROUPING SETS
はGROUP BY
句の中で使用します。基本的な構文は以下の通りです。
SELECT
column1,
column2,
集計関数(column3)
FROM
table_name
GROUP BY
GROUPING SETS ( (column1), (column2), ... );
先ほどのUNION ALL
で実現した「①部署ごとの合計給与」と「②役職ごとの合計給与」は、GROUPING SETS
を使えば次の一つのSQLで実現できます。
【GROUPING SETS を使ったSQL例】
SELECT
department,
position,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
GROUPING SETS ( (department), (position) );
実行結果とNULLの意味
上記のSQLを実行すると、結果は以下のようになります。UNION ALL
を使った時と似た結果が、よりシンプルなクエリで得られました。
department | position | total_salary |
---|---|---|
営業部 | NULL | 2200 |
開発部 | NULL | 1950 |
人事部 | NULL | 960 |
NULL | 部長 | 1650 |
NULL | 課長 | 1830 |
NULL | 一般 | 1630 |
ここで注目すべきはNULL
の存在です。
このNULL
は、データが元々NULL
であるという意味ではなく、「この行はその列でグループ化されていませんよ」ということを示す特別なNULL
です。例えば、department
が「営業部」でposition
がNULL
の行は、「部署ごとに集計した結果」であり、役職は考慮していないことを意味します。
複数列や総計の組み合わせも可能
GROUPING SETS
の強力な点は、複数列の組み合わせや、全データを対象とした総計(グランドトータル)も指定できることです。
(department, position)
:部署と役職の組み合わせで集計()
:全データを1つのグループとして集計(総計)
例えば、「部署ごと」「役職ごと」「部署と役職の組み合わせごと」「全体の合計」の4パターンを一度に取得したい場合は、以下のように記述します。
SELECT
department,
position,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
GROUPING SETS ( (department), (position), (department, position), () );
このようにGROUPING SETS
は、開発者が必要とする集計の組み合わせを柔軟かつ明示的に指定できる、非常に便利な機能です。
ROLLUPの使い方│階層的な小計・合計を自動生成するSQL
GROUPING SETS
がアラカルトだとしたら、ROLLUP
はコース料理のように決まったパターンの集計を自動的に行ってくれる機能です。特に、日次→月次→年次のような階層構造を持つデータの集計(ドリルダウン分析)で真価を発揮します。
ROLLUP
は、指定された列の組み合わせを、右側の列から一つずつ取り除きながら集計パターンを生成します。
基本的な使い方とSQL構文
ROLLUP(department, position)
と指定した場合、内部的には以下の3つの集計パターンを生成するGROUPING SETS
と等価になります。
(department, position)
:部署と役職ごとの集計(最も詳細)(department)
:部署ごとの小計()
:全体の合計(グランドトータル)
【ROLLUP を使ったSQL例】
SELECT
department,
position,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
ROLLUP (department, position);
実行結果とGROUPING SETS
との違い
上記のSQLを実行すると、結果は以下のようになります。
department | position | total_salary |
---|---|---|
営業部 | 部長 | 800 |
営業部 | 課長 | 600 |
営業部 | 一般 | 800 |
営業部 | NULL | 2200 |
開発部 | 部長 | 850 |
開発部 | 課長 | 650 |
開発部 | 一般 | 450 |
開発部 | NULL | 1950 |
人事部 | 課長 | 580 |
人事部 | 一般 | 380 |
人事部 | NULL | 960 |
NULL | NULL | 5110 |
GROUPING SETS
の例と見比べると違いは明らかです。ROLLUP
は(department, position)
の詳細な集計に加えて、部署ごとの小計(position
がNULL
の行)と、全体の合計(department
とposition
が両方NULL
の行)を自動的に追加してくれています。
ROLLUP
は指定された列の階層関係を意識し、「詳細→中項目→大項目」へと巻き上げて集計(ロールアップ)してくれるのです。これにより、集計パターンを一つずつ記述する手間が省け、コードもシンプルになります。
CUBEの使い方│全パターンのクロス集計を網羅するSQL
ROLLUP
が階層的な集計を得意とする一方、CUBE
は指定された列の考えられるすべての組み合わせを網羅的に集計します。階層関係を意識せず、サイコロ(キューブ)をあらゆる角度から眺めるように、多角的なクロス集計を行いたい場合に最適です。
ROLLUP
との違い
CUBE
とROLLUP
の最も大きな違いは、集計パターンの生成方法にあります。CUBE(department, position)
と指定した場合、内部的には以下の4つの全組み合わせを生成するGROUPING SETS
と等価になります。
(department, position)
:部署と役職ごとの集計(department)
:部署ごとの小計(position)
:役職ごとの小計 ← ROLLUPには無かったパターン()
:全体の合計
ROLLUP
が指定した順序に沿って階層的に集計するのに対し、CUBE
は順序を考慮せず、あらゆるパターンの集計結果を出力します。
基本的な使い方とSQL構文
【CUBE を使ったSQL例】
SELECT
department,
position,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
CUBE (department, position);
実行結果と考察
上記のSQLを実行すると、結果は以下のようになります。ROLLUP
の結果に加えて、department
がNULL
の「役職ごと」の集計行が追加されている点に注目してください。
department | position | total_salary |
---|---|---|
営業部 | 部長 | 800 |
営業部 | 課長 | 600 |
営業部 | 一般 | 800 |
営業部 | NULL | 2200 |
開発部 | 部長 | 850 |
開発部 | 課長 | 650 |
開発部 | 一般 | 450 |
開発部 | NULL | 1950 |
人事部 | 課長 | 580 |
人事部 | 一般 | 380 |
人事部 | NULL | 960 |
NULL | 部長 | 1650 |
NULL | 課長 | 1830 |
NULL | 一般 | 1630 |
NULL | NULL | 5110 |
このようにCUBE
を使えば、「部署ごとの特徴」と「役職ごとの特徴」という異なる軸の分析を一度のクエリで実行できます。ただし、集計対象の列が増えると組み合わせの数が爆発的に増加($2^n$通り)するため、パフォーマンスへの影響を考慮して使用する必要があります。
3つの違いを比較│GROUPING SETS・ROLLUP・CUBEの使い分け
ここまで解説してきた3つの機能を、改めて比較して整理しましょう。それぞれの特徴を理解し、目的の集計に最も適したものを選択することが、効率的なデータ分析の鍵となります。
機能比較一覧表
機能 | 特徴 | イメージ | こんなときにおすすめ |
---|---|---|---|
GROUPING SETS |
必要な集計パターンを 自由に、明示的に 指定する。 | アラカルト(単品注文) | ・決まったパターンのレポート作成 ・階層や網羅性が必要ない特定の集計 |
ROLLUP |
指定した列の 階層に従って 小計・合計を自動生成する。 | コース料理 | ・日次→月次→年次レポート ・国→都道府県→市区町村の売上集計 |
CUBE |
指定した列の すべての組み合わせ を網羅的に集計する。 | ビュッフェ(全種類) | ・多角的なクロス集計 ・データ探索や分析の初期段階 |
GROUPING
関数で集計行を分かりやすく
GROUPING SETS
、ROLLUP
、CUBE
を使うと、集計行にはNULL
が表示されます。しかし、元のデータがNULL
だった場合と区別がつきません。そこで役立つのがGROUPING
関数です。
GROUPING(列名)
は、その行が指定した列で集計された結果(NULL
が表示されている行)であれば1
を、そうでなければ0
を返します。
【GROUPING
関数を使ったSQL例】
SELECT
GROUPING(department) AS is_dept_total,
GROUPING(position) AS is_pos_total,
department,
position,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
CUBE (department, position)
ORDER BY
department, position;
このクエリを実行すると、どの列が集計対象になっているかが0
と1
で明確に判別できるようになり、アプリケーションでの処理や分析がより容易になります。例えばis_dept_total
が1
の行は、部署の垣根を越えた集計(役職ごとの集計や総計)であることを示します。