「部門ごとの社員名をカンマ区切りで1行にまとめたい」「注文IDに紐づくタグを一覧で表示したい」――こうしたグループ内の値を1つの文字列に結合する処理で活躍するのがLISTAGG関数です。Oracle 11g R2 で追加され、12c R2 でオーバーフロー制御、19c で DISTINCT が使えるようになりました。本記事では基本構文から4000バイト制限の対処法まで体系的に解説します。
- LISTAGG の基本構文と WITHIN GROUP (ORDER BY) の使い方
- GROUP BY との組み合わせでグループごとに文字列を結合する方法
- 区切り文字のカスタマイズ(カンマ・改行・パイプ等)
- DISTINCT で重複を除去して結合する方法(Oracle 19c 以降)
- ON OVERFLOW TRUNCATE でオーバーフローを安全に処理する方法(Oracle 12c R2 以降)
- 4000バイト制限を超える場合の XMLAGG 代替パターン
- NULL 値の扱いと注意点
LISTAGG の基本構文
LISTAGG(結合する列, 区切り文字)
WITHIN GROUP (ORDER BY ソート列)
最もシンプルな例として、全社員の名前をカンマ区切りで1行にまとめるSQLです。
SELECT
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS all_names
FROM employees;
-- 結果: 佐藤, 鈴木, 田中, 中村, 山田
| 要素 | 説明 |
|---|---|
| 第1引数(結合する列) | 結合したい列名または式 |
| 第2引数(区切り文字) | 値の間に挿入する文字列。省略するとNULL(区切りなしで連結) |
| WITHIN GROUP (ORDER BY) | 結合する際の並び順を指定する。必須 |
GROUP BY との組み合わせ
LISTAGGは集計関数なので、GROUP BYと組み合わせてグループごとに文字列を結合できます。
SELECT
department_id,
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS member_list
FROM employees
GROUP BY department_id
ORDER BY department_id;
| DEPARTMENT_ID | MEMBER_LIST |
|---|---|
| 10 | 佐藤, 鈴木, 山田 |
| 20 | 中村, 田中, 渡辺 |
| 30 | 伊藤, 小林 |
分析関数(OVER句)としての LISTAGG
LISTAGGは分析関数としても使えます。GROUP BY で行を集約せず、元の行数を保ったまま結合結果を各行に付与できます。
SELECT
employee_id,
name,
department_id,
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name)
OVER (PARTITION BY department_id) AS dept_members
FROM employees
ORDER BY department_id, name;
| EMPLOYEE_ID | NAME | DEPARTMENT_ID | DEPT_MEMBERS |
|---|---|---|---|
| 101 | 佐藤 | 10 | 佐藤, 鈴木, 山田 |
| 103 | 鈴木 | 10 | 佐藤, 鈴木, 山田 |
| 105 | 山田 | 10 | 佐藤, 鈴木, 山田 |
分析関数の詳しい使い方は「【Oracle】分析関数(ウィンドウ関数)の使い方完全ガイド」を参照してください。
区切り文字のカスタマイズ
第2引数にはどんな文字列でも指定できます。
-- カンマ + スペース(最も一般的) LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) -- → 佐藤, 鈴木, 山田 -- パイプ区切り(CSV代替) LISTAGG(name, ' | ') WITHIN GROUP (ORDER BY name) -- → 佐藤 | 鈴木 | 山田 -- 改行区切り(CLOBに出力する場合向け) LISTAGG(name, CHR(10)) WITHIN GROUP (ORDER BY name) -- → 佐藤 -- 鈴木 -- 山田 -- 区切りなし(連結) LISTAGG(name) WITHIN GROUP (ORDER BY name) -- → 佐藤鈴木山田 -- HTML向け(リスト項目として結合) LISTAGG(name, '</li><li>') WITHIN GROUP (ORDER BY name) -- → 佐藤</li><li>鈴木</li><li>山田
DISTINCT で重複を除去する(Oracle 19c 以降)
Oracle 19c からは LISTAGG 内で DISTINCT キーワードが使えるようになりました。同じ値が複数回出現する場合に重複を排除して結合できます。
-- 重複ありの場合
SELECT
department_id,
LISTAGG(DISTINCT job_title, ', ') WITHIN GROUP (ORDER BY job_title) AS job_list
FROM employees
GROUP BY department_id;
-- 同じ job_title が複数人いても1回だけ表示される
-- 結果例: エンジニア, マネージャー, リーダー
Oracle 18c 以前で重複を除去する方法
19c 未満の環境では LISTAGG(DISTINCT ...) が使えないため、サブクエリで事前に重複を排除します。
SELECT
department_id,
LISTAGG(job_title, ', ') WITHIN GROUP (ORDER BY job_title) AS job_list
FROM (
SELECT DISTINCT department_id, job_title
FROM employees
)
GROUP BY department_id;
ON OVERFLOW TRUNCATE でオーバーフローを制御する(Oracle 12c R2 以降)
LISTAGG の結果は最大4000バイト(VARCHAR2の上限)です。結合結果がこの上限を超えると ORA-01489: result of string concatenation is too long エラーが発生します。
Oracle 12c R2(12.2)以降では ON OVERFLOW TRUNCATE を指定することで、上限を超えた場合にエラーではなく切り詰めで対応できます。
SELECT
department_id,
LISTAGG(name, ', ' ON OVERFLOW TRUNCATE '...' WITH COUNT)
WITHIN GROUP (ORDER BY name) AS member_list
FROM employees
GROUP BY department_id;
-- 4000バイトを超える場合、末尾が「...(残り15件)」のように切り詰められる
| 構文 | 動作 |
|---|---|
ON OVERFLOW TRUNCATE |
デフォルトの切り詰め文字「…」を末尾に付ける |
ON OVERFLOW TRUNCATE '...' WITH COUNT |
切り詰め文字と省略件数を表示する(例: …(15)) |
ON OVERFLOW TRUNCATE '...' WITHOUT COUNT |
切り詰め文字のみ表示(件数なし) |
ON OVERFLOW TRUNCATE '' WITHOUT COUNT |
何も付けずに切り詰める(切り詰め文字なし) |
ON OVERFLOW ERROR |
ORA-01489 エラーを発生させる(デフォルト動作) |
4000バイト制限を超える場合の対処法(XMLAGG)
Oracle 12c R1 以前では ON OVERFLOW TRUNCATE が使えないため、結果が4000バイトを超えるとエラーになります。この場合は XMLAGG + XMLELEMENT を使って CLOB として結合する方法が定番です。
SELECT
department_id,
RTRIM(
XMLAGG(
XMLELEMENT(e, name || ', ')
ORDER BY name
).EXTRACT('//text()').GETCLOBVAL(),
', '
) AS member_list
FROM employees
GROUP BY department_id;
-- 結果は CLOB 型で返されるため、4000バイトの制限がない
XMLAGG は結果が CLOB 型になるため、VARCHAR2 を期待する箇所では TO_CHAR() での変換が必要です。また、XML特殊文字(&, <, > 等)がエンティティに変換されるため、データ中にこれらの文字が含まれる場合は注意してください。
NULL 値の扱い
LISTAGGはNULL値を自動的にスキップします。NVLを使えばNULLを任意の値で置換して結合に含めることもできます。
-- NULLは自動的にスキップされる
SELECT LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS names
FROM employees
WHERE department_id = 10;
-- name が NULL の行は結合結果に含まれない
-- NULLを「(未設定)」として結合に含めたい場合
SELECT
LISTAGG(NVL(name, '(未設定)'), ', ') WITHIN GROUP (ORDER BY name) AS names
FROM employees
WHERE department_id = 10;
実務パターン:注文に紐づくタグを一覧表示する
SELECT
o.order_id,
o.order_date,
o.total_amount,
LISTAGG(t.tag_name, ', ') WITHIN GROUP (ORDER BY t.tag_name) AS tags
FROM orders o
LEFT JOIN order_tags ot ON o.order_id = ot.order_id
LEFT JOIN tags t ON ot.tag_id = t.tag_id
GROUP BY o.order_id, o.order_date, o.total_amount
ORDER BY o.order_date DESC;
実務パターン:権限一覧をユーザーごとにまとめる
SELECT
grantee,
LISTAGG(privilege, ', ' ON OVERFLOW TRUNCATE WITH COUNT)
WITHIN GROUP (ORDER BY privilege) AS granted_privileges
FROM dba_sys_privs
WHERE grantee NOT IN ('SYS', 'SYSTEM')
GROUP BY grantee
ORDER BY grantee;
-- 結果例: HR → ALTER SESSION, CREATE TABLE, CREATE VIEW, ...
実務パターン:LISTAGG の結果を WHERE 条件で使う
LISTAGG は集計関数なので、WHERE ではなく HAVING で絞り込みます。
-- メンバーリストに「山田」を含む部門だけを表示する
SELECT
department_id,
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS member_list
FROM employees
GROUP BY department_id
HAVING LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) LIKE '%山田%'
ORDER BY department_id;
LISTAGG と代替手段の比較
| 方法 | 最大長 | DISTINCT | 対応バージョン | 用途 |
|---|---|---|---|---|
LISTAGG |
4000バイト | 19c以降 | 11g R2 以降 | 標準的な文字列結合 |
LISTAGG + ON OVERFLOW |
4000バイト(安全に切り詰め) | 19c以降 | 12c R2 以降 | オーバーフロー時のエラー回避 |
XMLAGG + XMLELEMENT |
CLOB(制限なし) | サブクエリで対応 | 全バージョン | 4000バイトを超える結合 |
まとめ
LISTAGGは、グループ内の値をカンマ区切り等で1行にまとめたいときに欠かせないOracle関数です。
- 基本構文は
LISTAGG(列, 区切り) WITHIN GROUP (ORDER BY ソート列)。WITHIN GROUP は必須 GROUP BYと組み合わせてグループ単位で結合するのが最も一般的な使い方- 分析関数(
OVER (PARTITION BY ...))としても使用可能で、行を集約せずに結合結果を付与できる - Oracle 19c 以降は
LISTAGG(DISTINCT ...)で重複除去が可能。18c以前はサブクエリで事前にDISTINCTする - Oracle 12c R2 以降は
ON OVERFLOW TRUNCATEで4000バイト超過時のエラーを回避できる - 4000バイト制限を根本的に超える必要がある場合は
XMLAGG+XMLELEMENTでCLOBとして結合する - NULL値は自動スキップされる。含めたい場合は
NVLで代替値を指定する

