【Oracle】LISTAGG関数の使い方完全ガイド|カンマ区切りで文字列を結合・DISTINCT・4000バイト制限の対処法

【Oracle】LISTAGG関数の使い方完全ガイド|カンマ区切りで文字列を結合・DISTINCT・4000バイト制限の対処法 Oracle

「部門ごとの社員名をカンマ区切りで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 ── 基本構文
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 で行を集約せず、元の行数を保ったまま結合結果を各行に付与できます。

分析関数としての LISTAGG ── 行を保ったまま結合結果を付ける
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 キーワードが使えるようになりました。同じ値が複数回出現する場合に重複を排除して結合できます。

LISTAGG(DISTINCT …) ── 重複除去(Oracle 19c+)
-- 重複ありの場合
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 ...) が使えないため、サブクエリで事前に重複を排除します。

18c 以前での 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 を指定することで、上限を超えた場合にエラーではなく切り詰めで対応できます。

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 として結合する方法が定番です。

XMLAGG ── 4000バイト制限を回避する代替パターン
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 の注意点
XMLAGG は結果が CLOB 型になるため、VARCHAR2 を期待する箇所では TO_CHAR() での変換が必要です。また、XML特殊文字(&, <, > 等)がエンティティに変換されるため、データ中にこれらの文字が含まれる場合は注意してください。

NULL 値の扱い

LISTAGGはNULL値を自動的にスキップします。NVLを使えばNULLを任意の値で置換して結合に含めることもできます。

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 で代替値を指定する