【完全ガイド】ORA-01489: result of string concatenation is too long の原因と解決方法|LISTAGG 4000 バイト・ON OVERFLOW TRUNCATE 徹底解説
- 作成日 2026.09.04
- Oracle Database その他
Oracle 開発者が集約クエリ実装時に頻繁に遭遇する典型的エラー:
SELECT department_id,
LISTAGG(last_name, '; ') WITHIN GROUP (ORDER BY last_name) AS emp_list
FROM employees
WHERE department_id = 50
GROUP BY department_id;
*
ERROR at line 2:
ORA-01489: result of string concatenation is too long
**「文字列連結の結果が長すぎる」**というシンプルなメッセージ。SQL 集約処理の実装で最頻出のサイズ系エラーです:
- LISTAGG の結果が 4000 バイト超過
- CSV / JSON エクスポートの失敗
- レポートクエリの実行エラー
- アプリ側で対処必須
- 12.2+ で
ON OVERFLOW TRUNCATEで優雅に対処可能
このエラーの本質は、Oracle の SQL レベル VARCHAR2 型のサイズ制限:
SQL VARCHAR2 デフォルト: 4000 バイト
LISTAGG 戻り値の型: VARCHAR2
→ 集約結果が 4000 バイト超過
→ ORA-01489 発生
- 1. 結論:ON OVERFLOW TRUNCATE か CLOB 化
- 2. Oracle VARCHAR2 サイズの仕組み
- 3. 【原因①】LISTAGG 結果 > 4000 バイト(最頻出)
- 4. 【原因②】CONCAT / || による大量結合
- 5. 【原因③】CSV / TSV エクスポート
- 6. 【原因④】JSON 生成
- 7. 【原因⑤】XML 生成
- 8. 【原因⑥】HTML レポート
- 9. 【原因⑦】サブクエリでの結合
- 10. 【原因⑧】動的 SQL 生成(IN 句)
- 11. 【原因⑨】監査ログ集約
- 12. 【原因⑩】Data Pump 関連
- 13. 診断ツール完全リファレンス
- 14. 6つの解決策 完全リファレンス
- 15. Rails / Java / Python 対応
- 16. 実践シナリオ
- 17. トラブルシューティング
- 18. よくある質問(FAQ)
- 19. 参考リンク
- 20. まとめ
SQL VARCHAR2 vs PL/SQL VARCHAR2 の重大な違い
日本語圏で混乱が多い重要な事実:
SQL VARCHAR2:
デフォルト: 4000 バイト(20年以上不変)
MAX_STRING_SIZE=EXTENDED: 32767 バイト(12c+)
PL/SQL VARCHAR2:
常に 32767 バイト
内部変数として利用
→ SQL 側で LISTAGG は 4000 バイト制限
→ PL/SQL 内で構築するなら 32767 バイト
→ CLOB を使えば 4GB まで
ORA-01489 は SQL レベルで発生、PL/SQL では ORA-06502(別エラー)。
姉妹エラーとの区別
文字列サイズ系エラー4種:
ORA-01489: result of string concatenation is too long
→ SQL レベル、VARCHAR2 4000 バイト超過(本記事)
ORA-06502: numeric or value error
→ PL/SQL レベル、VARCHAR2 32767 バイト超過等
ORA-12899: value too large for column
→ INSERT/UPDATE 時、テーブル列サイズ超過
ORA-01704: string literal too long
→ SQL リテラル、4000 バイト超過
現場で最も典型的なパターン:
- LISTAGG 結果 > 4000 バイト(超典型、最頻出)
- CONCAT / || による大量結合
- CSV / TSV エクスポート(複数行結合)
- JSON 生成(JSON_ARRAYAGG 未使用)
- XML 生成
- HTML レポート(複数行連結)
- サブクエリでの結合
- 動的 SQL 生成(IN 句の値列挙)
- 監査ログ集約
- Data Pump export(一部)
多くの日本語記事が「LISTAGG は避けよ」で終わりますが、実務では:
LISTAGG ON OVERFLOW TRUNCATE(12.2+ の決定版)XMLAGG + GETCLOBVAL()による古典的回避TO_CLOB(...)への変換MAX_STRING_SIZE = EXTENDEDによる DB 全体拡張JSON_ARRAYAGG(12c+)による CLOB 出力- PL/SQL でループ処理
SUBSTRによる切り詰めON OVERFLOW TRUNCATE 'terminator' WITH/WITHOUT COUNTの細かい制御- Rails / Java / Python でのCLOB 受け取り
さらに、Oracle 12c Release 2(12.2)で登場した ON OVERFLOW TRUNCATE は革命的な改善:
-- 12.2+
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY name) AS names
FROM emp;
-- 4000 バイト超えたら自動切り詰め + '...' 付加
-- エラーなし、優雅な対処
本記事では、ORA-01489: result of string concatenation is too long の完全な原因と解決方法を、リファレンスとして実用的に整理します。10大発生パターン、SQL vs PL/SQL の違い、6つの解決策、Rails/Java/Python 対応、実践シナリオ、FAQまで完全網羅。この1本で ORA-01489 を根本から解決できるようになります。
結論:ON OVERFLOW TRUNCATE か CLOB 化
時間がない方向けに、最速の対処を先に示します。
エラーメッセージの読み方
ORA-01489: result of string concatenation is too long
↑
連結結果が長すぎる
= SQL レベルで VARCHAR2 4000 バイト超過
LISTAGG や || で発生
12.2+ なら ON OVERFLOW TRUNCATE で対処
それ以外は CLOB / XMLAGG で
最速の対処(Oracle バージョン別)
12.2 以降(推奨):
SELECT LISTAGG(col, ',' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY col) AS result
FROM tab;
11g R2 以前 or CLOB 必要:
SELECT RTRIM(XMLAGG(XMLELEMENT(E, col || ',') ORDER BY col).EXTRACT('//text()').GETCLOBVAL(), ',') AS result
FROM tab;
12c+(JSON 用途):
SELECT JSON_ARRAYAGG(col ORDER BY col RETURNING CLOB) AS result
FROM tab;
PL/SQL に持ち込み:
DECLARE
v_result VARCHAR2(32767);
BEGIN
FOR r IN (SELECT col FROM tab ORDER BY col) LOOP
v_result := v_result || r.col || ',';
END LOOP;
END;
6つの解決策
| # | 手法 | 使う場面 |
|---|---|---|
| ① | ON OVERFLOW TRUNCATE | 12.2+、切り詰め OK |
| ② | XMLAGG + GETCLOBVAL | 11g R2 以前 |
| ③ | TO_CLOB 変換 | CLOB 全長必要 |
| ④ | MAX_STRING_SIZE=EXTENDED | 32767 まで拡張 |
| ⑤ | JSON_ARRAYAGG | JSON 用途 |
| ⑥ | PL/SQL ループ | 複雑処理 |
文字列サイズ系エラー4種
| エラー | 意味 |
|---|---|
| ORA-01489 | SQL 連結 4000 超過 |
| ORA-06502 | PL/SQL VARCHAR2 |
| ORA-12899 | 列サイズ超過 |
| ORA-01704 | リテラル 4000 超過 |
詳細は以下で解説します。
Oracle VARCHAR2 サイズの仕組み
VARCHAR2 の階層
【SQL レベル】
デフォルト:
VARCHAR2: 4000 バイト
NVARCHAR2: 4000 バイト(キャラクタ数)
RAW: 2000 バイト
拡張 (MAX_STRING_SIZE = EXTENDED、12c+):
VARCHAR2: 32767 バイト
NVARCHAR2: 32767 バイト
RAW: 32767 バイト
【PL/SQL レベル】
常に 32767 バイト
【CLOB】
最大 4GB (128TB with BIGFILE)
MAX_STRING_SIZE パラメータ(12c+)
-- 現状確認
SHOW PARAMETER max_string_size;
-- STANDARD (デフォルト): 4000 バイト
-- EXTENDED: 32767 バイト
-- 変更(非常に注意深く)
-- 1. DB 停止
-- 2. STARTUP UPGRADE
-- 3. utl32k.sql 実行
-- 4. パラメータ変更
-- 5. 再起動
⚠️ MAX_STRING_SIZE の変更は一方通行(STANDARD → EXTENDED のみ、戻せない)。
LISTAGG の戻り値型
LISTAGG の戻り値:
引数が VARCHAR2 なら VARCHAR2
引数が NVARCHAR2 なら NVARCHAR2
→ SQL レベル、4000 バイト制限
【原因①】LISTAGG 結果 > 4000 バイト(最頻出)
症状(超典型)
SELECT department_id,
LISTAGG(employee_name, ', ')
WITHIN GROUP (ORDER BY employee_name) AS employees
FROM employees
GROUP BY department_id;
-- ORA-01489
大部門で 100+ 従業員を連結 → 4000 バイト超過。
解決A: ON OVERFLOW TRUNCATE(12.2+、推奨)
-- 基本形('...' で末尾表示)
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY name) AS employees
FROM employees;
-- 出力例: "Alice, Bob, ..., ..., (3)"
-- 最後の (3) は省略件数
-- カスタム終了記号
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE '~~~')
WITHIN GROUP (ORDER BY name)
FROM employees;
-- 件数表示なし
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE WITHOUT COUNT)
WITHIN GROUP (ORDER BY name)
FROM employees;
-- 明示的エラー(デフォルト動作)
SELECT LISTAGG(name, ', ' ON OVERFLOW ERROR)
WITHIN GROUP (ORDER BY name)
FROM employees;
解決B: XMLAGG + GETCLOBVAL(全バージョン)
SELECT RTRIM(
XMLAGG(XMLELEMENT(E, employee_name || ', ')
ORDER BY employee_name).EXTRACT('//text()').GETCLOBVAL(),
', '
) AS employees
FROM employees;
-- CLOB で返却、4GB まで
解決C: TO_CLOB 変換
-- 12c+
SELECT LISTAGG(TO_CLOB(name || ', '))
WITHIN GROUP (ORDER BY name)
FROM employees;
【原因②】CONCAT / || による大量結合
シナリオ
SELECT MAX(col1 || col2 || col3 || col4 || col5) AS combined
FROM huge_table;
-- 各列が 1000 バイト → 5000 バイト → ORA-01489
解決
A. CLOB 変換:
SELECT MAX(TO_CLOB(col1) || col2 || col3 || col4 || col5) AS combined
FROM huge_table;
-- 最初を TO_CLOB で他も CLOB として扱う
B. SUBSTR で切り詰め:
SELECT MAX(SUBSTR(col1 || col2 || col3, 1, 4000)) AS combined
FROM huge_table;
【原因③】CSV / TSV エクスポート
シナリオ
-- CSV 出力用集約
SELECT LISTAGG(id || ',' || name || ',' || email || CHR(10))
WITHIN GROUP (ORDER BY id) AS csv_data
FROM customers;
-- 大量レコードで超過
解決
A. ON OVERFLOW TRUNCATE:
SELECT LISTAGG(id || ',' || name || ',' || email || CHR(10),
'' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY id) AS csv_data
FROM customers;
B. PL/SQL + UTL_FILE:
DECLARE
v_file UTL_FILE.FILE_TYPE;
BEGIN
v_file := UTL_FILE.FOPEN('DATA_DIR', 'customers.csv', 'W', 32767);
FOR r IN (SELECT id, name, email FROM customers ORDER BY id) LOOP
UTL_FILE.PUT_LINE(v_file, r.id || ',' || r.name || ',' || r.email);
END LOOP;
UTL_FILE.FCLOSE(v_file);
END;
/
【原因④】JSON 生成
シナリオ
SELECT LISTAGG(
'"' || name || '":' || salary || ',',
' '
) WITHIN GROUP (ORDER BY name) AS json_str
FROM emp;
-- 大量データで超過
解決: JSON_ARRAYAGG(12c+、推奨)
-- CLOB で返却
SELECT JSON_ARRAYAGG(
JSON_OBJECT('name' VALUE name, 'salary' VALUE salary)
ORDER BY name
RETURNING CLOB
) AS json_result
FROM emp;
-- 結果は完全な JSON、サイズ制限なし
【原因⑤】XML 生成
シナリオ
SELECT LISTAGG(
'<emp><id>' || id || '</id><name>' || name || '</name></emp>'
) WITHIN GROUP (ORDER BY id) AS xml_str
FROM emp;
解決: XMLAGG + GETCLOBVAL
SELECT XMLELEMENT("employees",
XMLAGG(
XMLELEMENT("emp",
XMLFOREST(id, name)
) ORDER BY id
)
).GETCLOBVAL() AS xml_result
FROM emp;
【原因⑥】HTML レポート
シナリオ
-- HTML テーブル生成
SELECT '<table>' || LISTAGG(
'<tr><td>' || id || '</td><td>' || name || '</td></tr>'
) WITHIN GROUP (ORDER BY id) || '</table>' AS html
FROM emp;
解決
PL/SQL でループ:
DECLARE
v_html CLOB;
BEGIN
DBMS_LOB.CREATETEMPORARY(v_html, TRUE);
DBMS_LOB.WRITEAPPEND(v_html, 7, '<table>');
FOR r IN (SELECT id, name FROM emp ORDER BY id) LOOP
DECLARE
v_row VARCHAR2(4000);
BEGIN
v_row := '<tr><td>' || r.id || '</td><td>' || r.name || '</td></tr>';
DBMS_LOB.WRITEAPPEND(v_html, LENGTH(v_row), v_row);
END;
END LOOP;
DBMS_LOB.WRITEAPPEND(v_html, 8, '</table>');
-- v_html を使用
END;
/
【原因⑦】サブクエリでの結合
シナリオ
SELECT
(SELECT LISTAGG(name, ',') FROM emp WHERE dept_id = d.id) AS emp_list
FROM dept d;
-- 大部門で超過
解決: ON OVERFLOW TRUNCATE
SELECT
(SELECT LISTAGG(name, ',' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY name)
FROM emp WHERE dept_id = d.id) AS emp_list
FROM dept d;
【原因⑧】動的 SQL 生成(IN 句)
シナリオ
DECLARE
v_ids VARCHAR2(4000);
v_sql VARCHAR2(32767);
BEGIN
-- ID リスト取得
SELECT LISTAGG(id, ',') WITHIN GROUP (ORDER BY id)
INTO v_ids
FROM large_table
WHERE flag = 'Y';
-- ORA-01489 or ORA-06502
END;
/
解決
A. PL/SQL で 32767 まで:
DECLARE
v_ids VARCHAR2(32767);
BEGIN
FOR r IN (SELECT id FROM large_table WHERE flag = 'Y') LOOP
v_ids := v_ids || r.id || ',';
IF LENGTH(v_ids) > 30000 THEN
EXIT; -- 制限
END IF;
END LOOP;
END;
B. Bind Variable / Array 使用(推奨):
-- IN 句を BULK COLLECT で
SELECT * FROM t WHERE id IN (SELECT column_value FROM TABLE(id_array));
【原因⑨】監査ログ集約
シナリオ
SELECT LISTAGG(action || '@' || TO_CHAR(action_time, 'YYYY-MM-DD HH24:MI:SS'), '; ')
WITHIN GROUP (ORDER BY action_time)
FROM audit_log
WHERE user_id = 100
AND action_time >= SYSDATE - 30;
-- 大量ログで超過
解決
-- ページング + CLOB
SELECT XMLAGG(XMLELEMENT(E, action || '@' || TO_CHAR(action_time))
ORDER BY action_time).GETCLOBVAL()
FROM audit_log
WHERE user_id = 100
AND action_time >= SYSDATE - 30;
【原因⑩】Data Pump 関連
シナリオ
Data Pump export/import で内部エラー
特定オブジェクトの DDL が 4000 バイト超過
対処
Data Pump パラメータ調整 or オブジェクト分割。
Data Pump 関連は Oracle Data Pump 使い方の記事も参照してください。
診断ツール完全リファレンス
現状確認
-- MAX_STRING_SIZE 確認
SHOW PARAMETER max_string_size;
-- Oracle バージョン
SELECT * FROM v$version;
-- LISTAGG のサポート機能
-- 12.2+ で ON OVERFLOW TRUNCATE 使用可
集約サイズ事前予測
-- 予想バイト数
SELECT SUM(LENGTHB(name) + 2) AS estimated_bytes
FROM emp
WHERE dept_id = 50;
-- 4000 超えるなら要対策
実行結果の長さ確認
-- CLOB の場合
SELECT DBMS_LOB.GETLENGTH(result) FROM (
SELECT XMLAGG(XMLELEMENT(E, name || ',')).GETCLOBVAL() AS result
FROM emp
);
PL/SQL でのサイズ検証
DECLARE
v_result VARCHAR2(32767);
BEGIN
SELECT LISTAGG(name, ',' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY name)
INTO v_result
FROM emp;
DBMS_OUTPUT.PUT_LINE('Length: ' || LENGTH(v_result));
END;
/
6つの解決策 完全リファレンス
解決策① LISTAGG ON OVERFLOW TRUNCATE(12.2+)
-- 基本形
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY name)
FROM emp;
-- 例: "Alice, Bob, Carol, ..., ..., (10)"
-- カスタム記号
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE 'MORE')
WITHIN GROUP (ORDER BY name)
FROM emp;
-- 例: "Alice, Bob, Carol, MORE(10)"
-- 件数表示なし
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE WITHOUT COUNT)
WITHIN GROUP (ORDER BY name)
FROM emp;
-- 例: "Alice, Bob, Carol, ..."
-- 明示的エラー(デフォルト)
SELECT LISTAGG(name, ', ' ON OVERFLOW ERROR)
WITHIN GROUP (ORDER BY name)
FROM emp;
-- 超過時 ORA-01489
解決策② XMLAGG + GETCLOBVAL
-- 全バージョン対応、CLOB で返却
SELECT RTRIM(
XMLAGG(XMLELEMENT(E, name || ',')
ORDER BY name).EXTRACT('//text()').GETCLOBVAL(),
','
) AS result
FROM emp;
注意: XML エスケープが発生(<, >, & 等)。
XML エスケープ回避版:
SELECT DBMS_XMLGEN.CONVERT(
RTRIM(
XMLAGG(XMLELEMENT(E, name || ',')
ORDER BY name).EXTRACT('//text()').GETCLOBVAL(),
','
),
1 -- Decode(アンエスケープ)
) AS result
FROM emp;
解決策③ TO_CLOB 変換
-- 12c+
SELECT LISTAGG(TO_CLOB(name), ',')
WITHIN GROUP (ORDER BY name) AS result
FROM emp;
-- 戻り値が CLOB になり制限なし
解決策④ MAX_STRING_SIZE = EXTENDED(DB 全体)
-- 現状
SHOW PARAMETER max_string_size;
-- STANDARD
-- ⚠️ 変更手順(一方通行!)
-- 1. バックアップ取得
-- 2. SHUTDOWN
-- 3. STARTUP UPGRADE
-- 4. ALTER SYSTEM SET MAX_STRING_SIZE = EXTENDED SCOPE = SPFILE;
-- 5. @?/rdbms/admin/utl32k.sql
-- 6. SHUTDOWN
-- 7. STARTUP
-- 効果: SQL VARCHAR2 も 32767 まで拡張
解決策⑤ JSON_ARRAYAGG(12c+)
-- CLOB で返却
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id' VALUE id,
'name' VALUE name,
'salary' VALUE salary
)
ORDER BY id
RETURNING CLOB
) AS json_data
FROM emp;
-- 出力例:
-- [{"id":1,"name":"Alice","salary":5000},...]
解決策⑥ PL/SQL でループ処理
DECLARE
v_result CLOB;
BEGIN
DBMS_LOB.CREATETEMPORARY(v_result, TRUE);
FOR r IN (SELECT name FROM emp ORDER BY name) LOOP
DBMS_LOB.WRITEAPPEND(v_result, LENGTH(r.name || ','), r.name || ',');
END LOOP;
-- 末尾カンマ削除
IF DBMS_LOB.GETLENGTH(v_result) > 0 THEN
DBMS_LOB.TRIM(v_result, DBMS_LOB.GETLENGTH(v_result) - 1);
END IF;
-- v_result 使用
DBMS_LOB.FREETEMPORARY(v_result);
END;
/
Rails / Java / Python 対応
Rails ActiveRecord
LISTAGG を CLOB で取得:
result = ActiveRecord::Base.connection.select_value(<<-SQL)
SELECT LISTAGG(TO_CLOB(name), ',')
WITHIN GROUP (ORDER BY name) AS result
FROM employees
SQL
# result は文字列(Ruby でサイズ制限なし)
エラーハンドリング:
begin
result = Employee.pluck(:name).join(',')
# または SQL 直接
rescue ActiveRecord::StatementInvalid => e
if e.message.include?("ORA-01489")
Rails.logger.error "String concatenation too long"
# ON OVERFLOW TRUNCATE 版で再試行
result = ActiveRecord::Base.connection.select_value(<<-SQL)
SELECT LISTAGG(name, ',' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY name) FROM employees
SQL
end
end
Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、find/find_by/where の記事も参照してください。
Java (JDBC)
CLOB を Reader で取得:
String sql = "SELECT XMLAGG(XMLELEMENT(E, name || ',') " +
"ORDER BY name).GETCLOBVAL() AS result FROM emp";
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
Clob clob = rs.getClob("result");
try (Reader r = clob.getCharacterStream()) {
StringBuilder sb = new StringBuilder();
char[] buf = new char[8192];
int len;
while ((len = r.read(buf)) != -1) {
sb.append(buf, 0, len);
}
String result = sb.toString();
}
}
}
Python (oracledb)
import oracledb
# CLOB を文字列で取得
cursor.execute("""
SELECT LISTAGG(TO_CLOB(name), ',')
WITHIN GROUP (ORDER BY name) AS result
FROM employees
""")
result = cursor.fetchone()[0]
# CLOB オブジェクトの場合
if hasattr(result, 'read'):
result = result.read()
実践シナリオ
シナリオ1:レポート SQL の 12.2+ 対応
-- Before(4000 バイトで失敗の可能性)
SELECT dept_id,
LISTAGG(emp_name, ', ') WITHIN GROUP (ORDER BY emp_name) AS members
FROM emp
GROUP BY dept_id;
-- After(安全)
SELECT dept_id,
LISTAGG(emp_name, ', ' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY emp_name) AS members
FROM emp
GROUP BY dept_id;
シナリオ2:CSV エクスポート
-- PL/SQL + UTL_FILE で大量データ出力
DECLARE
v_file UTL_FILE.FILE_TYPE;
BEGIN
v_file := UTL_FILE.FOPEN('CSV_DIR', 'emp_' || TO_CHAR(SYSDATE, 'YYYYMMDD') || '.csv', 'W', 32767);
-- ヘッダー
UTL_FILE.PUT_LINE(v_file, 'id,name,dept,salary');
-- データ
FOR r IN (SELECT id, name, dept_id, salary FROM emp ORDER BY id) LOOP
UTL_FILE.PUT_LINE(v_file,
r.id || ',' ||
'"' || r.name || '",' ||
r.dept_id || ',' ||
r.salary);
END LOOP;
UTL_FILE.FCLOSE(v_file);
END;
/
シナリオ3:JSON API レスポンス
-- JSON_ARRAYAGG で CLOB
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id' VALUE emp_id,
'name' VALUE emp_name,
'dept' VALUE dept_name,
'salary' VALUE salary
)
ORDER BY emp_id
RETURNING CLOB
) AS api_response
FROM emp_view;
シナリオ4:11g R2 環境での回避
-- ON OVERFLOW TRUNCATE 使えない環境
SELECT RTRIM(
DBMS_XMLGEN.CONVERT(
XMLAGG(XMLELEMENT(E, emp_name || ', ')
ORDER BY emp_name).EXTRACT('//text()').GETCLOBVAL(),
1
),
', '
) AS members
FROM emp;
シナリオ5:Rails でのレポート機能
class DepartmentReport
def self.member_lists
ActiveRecord::Base.connection.select_all(<<-SQL).to_a
SELECT
dept_id,
LISTAGG(emp_name, ', ' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY emp_name) AS members
FROM employees
GROUP BY dept_id
SQL
end
end
Rails 8 系の詳細は Rails 8 アップグレードガイドの記事も参照してください。
シナリオ6:Java Spring レポート
@Query(value = """
SELECT dept_id,
LISTAGG(emp_name, ',' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY emp_name) AS members
FROM employees
GROUP BY dept_id
""", nativeQuery = true)
List<DeptMemberDto> findAllDeptMembers();
シナリオ7:Python バッチ処理
import oracledb
def export_dept_members():
with oracledb.connect(user=user, password=pw, dsn=dsn) as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT dept_id,
LISTAGG(emp_name, ',' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY emp_name) AS members
FROM employees
GROUP BY dept_id
""")
for dept_id, members in cursor:
print(f"Dept {dept_id}: {members}")
シナリオ8:Docker Oracle でのテスト
docker exec -it oracle-xe sqlplus scott/tiger <<EOF
CREATE TABLE t (id NUMBER, val VARCHAR2(200));
INSERT INTO t SELECT LEVEL, LPAD('X', 100, 'X')
FROM dual CONNECT BY LEVEL <= 100;
COMMIT;
-- 通常 LISTAGG → 超過
SELECT LISTAGG(val, ',') WITHIN GROUP (ORDER BY id) FROM t;
-- ORA-01489
-- ON OVERFLOW TRUNCATE
SELECT LISTAGG(val, ',' ON OVERFLOW TRUNCATE) WITHIN GROUP (ORDER BY id) FROM t;
-- 成功
-- XMLAGG
SELECT DBMS_LOB.GETLENGTH(
XMLAGG(XMLELEMENT(E, val || ',')).EXTRACT('//text()').GETCLOBVAL()
) AS byte_len FROM t;
EOF
Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。
シナリオ9:MAX_STRING_SIZE 導入検討
-- 影響評価
-- 1. Extended データ型テーブル確認
SELECT COUNT(*) FROM dba_tab_columns
WHERE data_type = 'VARCHAR2' AND data_length > 4000;
-- 2. アプリ影響評価(列サイズが変わるわけではない)
-- SQL 関数の戻り値のみ影響
-- 3. 実施(バックアップ後)
-- SHUTDOWN → STARTUP UPGRADE
-- ALTER SYSTEM SET MAX_STRING_SIZE = EXTENDED SCOPE = SPFILE;
-- @?/rdbms/admin/utl32k.sql
-- 再起動
シナリオ10:Autonomous DB
Autonomous DB では:
- MAX_STRING_SIZE = EXTENDED がデフォルト
- SQL VARCHAR2 も 32767 まで
- 4000 バイト制限緩和
- LISTAGG も 32767 まで
トラブルシューティング
ON OVERFLOW TRUNCATE 使えない
Oracle バージョン確認、12.2 以降必須。
XMLAGG で XML エスケープ
DBMS_XMLGEN.CONVERT or XMLCAST でデコード。
CLOB を Rails で受け取れない
oracle_enhanced adapter の設定確認、activerecord-oracle_enhanced-adapter バージョン。
JSON_ARRAYAGG が使えない
Oracle 12c+ 必須、それ以前は手動 JSON 構築。
PL/SQL でも 32767 超える
CLOB 変数使用:
v_result CLOB;
DBMS_LOB.CREATETEMPORARY(v_result, TRUE);
PostgreSQL からの移行
PG の STRING_AGG は制限なし、Oracle 移行時は要注意。
よくある質問(FAQ)
Q1. SQL VARCHAR2 と PL/SQL VARCHAR2
- SQL: 4000 バイト(EXTENDED で 32767)
- PL/SQL: 常に 32767 バイト
Q2. ON OVERFLOW TRUNCATE の版
Oracle 12c Release 2 (12.2)+ で使用可。
Q3. データ切り詰めのリスク
ON OVERFLOW TRUNCATE はデータ損失、要件次第。
Q4. XMLAGG の副作用
XML エスケープ発生、DBMS_XMLGEN.CONVERT で対処。
Q5. MAX_STRING_SIZE 変更
一方通行、STANDARD → EXTENDED のみ、慎重に。
Q6. JSON_ARRAYAGG 推奨場面
JSON 用途なら 12c+ で JSON_ARRAYAGG 一択。
Q7. Rails での対応
ON OVERFLOW TRUNCATE or CLOB 受け取り。
Q8. Java での対応
Clob.getCharacterStream() で読み取り。
Q9. Python での対応
oracledb の CLOB 対応、.read() メソッド。
Q10. パフォーマンス影響
XMLAGG は遅い、CLOB 生成コスト、ON OVERFLOW TRUNCATE 推奨。
Q11. Autonomous DB での挙動
MAX_STRING_SIZE = EXTENDED デフォルト、4000 制限緩和。
Q12. 予防策
- 12.2+ なら ON OVERFLOW TRUNCATE デフォルト
- CLOB 受け取り体制
- サイズ事前予測
- CSV は UTL_FILE 使用
参考リンク
Oracle 公式
- Oracle Database Error Messages: ORA-01489
- Oracle Database SQL Language Reference: LISTAGG
- Oracle Database Reference: MAX_STRING_SIZE
- Oracle Database SQL Language Reference: JSON_ARRAYAGG
まとめ
ORA-01489: result of string concatenation is too long の要点を再整理します。
エラーの本質
SQL レベルで文字列連結結果が VARCHAR2 4000 バイト超過
→ LISTAGG や || で発生
→ 12.2+ なら ON OVERFLOW TRUNCATE で優雅に対処
→ 全バージョンは XMLAGG + GETCLOBVAL
エラーメッセージの読み方
ORA-01489: result of string concatenation is too long
↑
連結結果が長すぎる
VARCHAR2 サイズ階層
| レベル | サイズ |
|---|---|
| SQL VARCHAR2 (default) | 4000 バイト |
| SQL VARCHAR2 (EXTENDED) | 32767 バイト |
| PL/SQL VARCHAR2 | 32767 バイト(常に) |
| CLOB | 4GB |
文字列サイズ系エラー4種
| エラー | 意味 |
|---|---|
| ORA-01489 | SQL 連結 4000 超過 |
| ORA-06502 | PL/SQL VARCHAR2 |
| ORA-12899 | 列サイズ超過 |
| ORA-01704 | リテラル 4000 超過 |
10大原因
| # | 原因 | 対処 |
|---|---|---|
| ① | LISTAGG > 4000 | ON OVERFLOW TRUNCATE |
| ② | || 大量結合 | TO_CLOB |
| ③ | CSV エクスポート | UTL_FILE |
| ④ | JSON 生成 | JSON_ARRAYAGG |
| ⑤ | XML 生成 | XMLAGG |
| ⑥ | HTML レポート | PL/SQL + CLOB |
| ⑦ | サブクエリ | ON OVERFLOW TRUNCATE |
| ⑧ | 動的 SQL | Array/Bind |
| ⑨ | 監査ログ集約 | XMLAGG |
| ⑩ | Data Pump | パラメータ調整 |
6つの解決策
-- ① ON OVERFLOW TRUNCATE(12.2+、推奨)
SELECT LISTAGG(col, ',' ON OVERFLOW TRUNCATE)
WITHIN GROUP (ORDER BY col) FROM tab;
-- ② XMLAGG + GETCLOBVAL(全バージョン)
SELECT RTRIM(
XMLAGG(XMLELEMENT(E, col || ',') ORDER BY col)
.EXTRACT('//text()').GETCLOBVAL(), ','
) FROM tab;
-- ③ TO_CLOB 変換
SELECT LISTAGG(TO_CLOB(col), ',')
WITHIN GROUP (ORDER BY col) FROM tab;
-- ④ MAX_STRING_SIZE = EXTENDED
-- DB 全体で 32767 まで
-- ⑤ JSON_ARRAYAGG(12c+)
SELECT JSON_ARRAYAGG(col ORDER BY col RETURNING CLOB)
FROM tab;
-- ⑥ PL/SQL でループ
DECLARE v_result CLOB;
BEGIN
DBMS_LOB.CREATETEMPORARY(v_result, TRUE);
FOR r IN (SELECT col FROM tab) LOOP
DBMS_LOB.WRITEAPPEND(v_result, LENGTH(r.col), r.col);
END LOOP;
END;
ON OVERFLOW TRUNCATE の詳細(12.2+)
-- 基本
LISTAGG(col, ',' ON OVERFLOW TRUNCATE)
-- 出力: "A,B,C,...,(N)" N = 省略件数
-- カスタム記号
LISTAGG(col, ',' ON OVERFLOW TRUNCATE '~~~')
-- 出力: "A,B,C,~~~,(N)"
-- 件数なし
LISTAGG(col, ',' ON OVERFLOW TRUNCATE WITHOUT COUNT)
-- 出力: "A,B,C,..."
-- 明示エラー(デフォルト)
LISTAGG(col, ',' ON OVERFLOW ERROR)
-- 超過時 ORA-01489
各言語での対応
Rails: ON OVERFLOW TRUNCATE or CLOB 受け取り
Java: Clob.getCharacterStream()
Python: oracledb で LOB.read()
共通: バージョン確認、CLOB 対応強化
予防のポイント
1. Oracle 12.2+ なら ON OVERFLOW TRUNCATE 標準採用
2. 集約サイズを事前予測
3. JSON は JSON_ARRAYAGG (12c+)
4. XML は XMLAGG
5. CSV は UTL_FILE
6. Rails/Java/Python で CLOB 対応
7. MAX_STRING_SIZE=EXTENDED は影響評価後
8. Autonomous DB では気にせず使える
9. PostgreSQL 移行時は STRING_AGG 変換注意
10. リファクタリング機会に対応
これらの知識は、Oracle での SQL 開発・レポート機能・CSV/JSON エクスポート・API 開発・監査ログ集約・Rails / Java / Python アプリ運用・データ移行・CI/CD パイプライン・Autonomous DB 活用など、あらゆる場面で活用できます。本記事をブックマークしておけば、ORA-01489 に出会っても冷静に的確に対処できるようになります。
本記事は2026年6月時点の情報をもとに、Oracle Database 12c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。
-
前の記事
【完全ガイド】ORA-08102: index key not found の原因と解決方法|インデックス破損・REBUILD の罠 徹底解説 2026.09.03
-
次の記事
【完全ガイド】ORA-01407: cannot update … to NULL の原因と解決方法|相関サブクエリ・NVL 外側 vs 内側 徹底解説 2026.09.07
コメントを書く