【Oracle】ORA-01704の原因と解決方法|string literal too long・長い文字列の入れ方

【Oracle】ORA-01704の原因と解決方法|string literal too long・長い文字列の入れ方 Oracle

ORA-01704: string literal too long は、SQL文の中に直接書いた文字列リテラルが長すぎる時に発生するOracleエラーです。CLOB列にINSERTしている場合でも、SQLに '長い文字列' をそのまま書くと、列の型ではなく「SQLリテラルの上限」に先に当たります。

よくあるのは、長いJSON、XML、HTML、メール本文、ログ本文、Base64文字列をINSERT文に直接埋め込むケースです。この記事では、原因、4000バイト制限の考え方、CLOBへの正しい入れ方、バインド変数や DBMS_LOB を使った回避策を整理します。

先に結論
ORA-01704は、CLOB列の容量不足ではなく、SQLに直接書いた文字列リテラルが長すぎることが主因です。長文データはSQL文字列に埋め込まず、アプリやPL/SQLのバインド変数、DBMS_LOB.APPEND、SQL*Loader、Data Pumpなどで渡すのが安全です。
スポンサーリンク

ORA-01704とは

Oracle公式のエラー説明では、ORA-01704は文字列リテラルが長すぎる場合に発生し、対処として短い文字列リテラルを使うことが示されています。SQL Language Referenceでは、テキストリテラルの最大長は MAX_STRING_SIZE=STANDARD で4000バイト、MAX_STRING_SIZE=EXTENDED で32767バイトとされています。

ただし、EXTENDEDにすれば何でも解決するわけではありません。数十KB、数MBのJSONやXMLを扱うなら、SQLリテラルではなくCLOBとして渡す設計にするのが本筋です。CLOBやBLOBの基本は LOB(CLOB/BLOB/BFILE)完全ガイド も参考になります。

発生しやすいSQL

次のように、INSERT文へ長い文字列を直接書くとORA-01704になりやすいです。列がCLOBでも、SQL内の '...' が長すぎる時点でエラーになります。

bad-long-literal-insert.sql
INSERT INTO message_log (id, body)
VALUES (
  1,
  'ここに4000バイトを超える長いJSON、XML、HTML、メール本文などを直接書く'
);

動的SQLでも同じです。文字列連結で巨大なSQL文を作ると、SQLリテラルの上限、PL/SQL変数の上限、SQL文そのものの扱いづらさが重なります。動的SQLの基本は EXECUTE IMMEDIATE完全ガイド にまとめています。

bad-dynamic-sql.sql
DECLARE
  v_sql  CLOB;
  v_body CLOB;
BEGIN
  v_body := 'ここに長いJSONやHTML本文が入る';
  v_sql  := 'INSERT INTO message_log(id, body) VALUES (1, ''' || v_body || ''')';
  EXECUTE IMMEDIATE v_sql;
END;
/

原因別の対処

INSERT文に長文を直書きしている

文字列をSQLに埋め込まず、アプリ側のバインド変数、PL/SQL変数、SQL*Loaderなどで渡します。CLOB列でも直書きは避けます。

長いJSON/XMLを保存したい

CLOB列を使い、バインド変数またはDBMS_LOBで書き込みます。JSON専用の設計が必要な場合はJSON型や制約も検討します。

移行SQLに長文が含まれている

大量のINSERT文ではなく、SQL*Loader、Data Pump、外部表、アプリ経由のバインド投入へ切り替えます。

動的SQLで長文を組み立てている

値をSQL文字列に連結せず、バインド変数に逃がします。SQLインジェクション対策としてもこの形が安全です。

対処1: バインド変数で渡す

アプリケーションから登録する場合は、長文をSQL文字列へ連結せず、バインド変数として渡します。これならSQLリテラル上限を避けやすく、SQLインジェクション対策にもなります。

bind-variable-insert.sql
INSERT INTO message_log (id, body)
VALUES (:id, :body_clob);

JDBC、ODP.NET、Python、PHPなどのドライバでは、CLOBや長い文字列をパラメータとして渡します。SQL文は固定し、値はバインドで渡すのが基本です。SQL本文やバインド周辺の調査では V$SQLAREA・V$SQLTEXTとは も役立ちます。

対処2: PL/SQL変数からCLOBへ入れる

PL/SQL内で扱う場合は、SQL文に長文リテラルを直接書くのではなく、変数として扱います。ただし、PL/SQLの VARCHAR2 にも上限があるため、非常に大きいデータはCLOBとして扱います。

insert-clob-from-variable.sql
DECLARE
  v_body CLOB;
BEGIN
  v_body := TO_CLOB('短い断片1') || TO_CLOB('短い断片2');

  INSERT INTO message_log (id, body)
  VALUES (1, v_body);
END;
/

この方法は、断片がそれぞれリテラル上限内に収まる場合の簡易策です。巨大なファイルや長いJSONを毎回SQLに埋め込む用途には向きません。

TO_CLOBで囲んでも直らないケース

ORA-01704でよくある誤解が、長いリテラルを TO_CLOB で囲めば解決する、というものです。しかし、次のように1つのリテラル自体が長すぎる場合、TO_CLOB が評価される前にリテラル上限へ引っかかります。

bad-to-clob-long-literal.sql
INSERT INTO message_log (id, body)
VALUES (
  1,
  TO_CLOB('ここに4000バイトを超える長い文字列を1つのリテラルとして書く')
);

TO_CLOB を使うなら、各断片をリテラル上限内に収めて連結します。ただし、長文データをSQLファイル内に無理やり持たせるより、バインド変数や外部ファイル取り込みへ逃がす方が保守しやすいです。

ok-to-clob-short-chunks.sql
INSERT INTO message_log (id, body)
VALUES (
  1,
  TO_CLOB('短い断片1') ||
  TO_CLOB('短い断片2') ||
  TO_CLOB('短い断片3')
);

対処3: DBMS_LOB.APPENDで分割して書く

DB内でCLOBを組み立てる必要がある場合は、一時CLOBを作り、短い断片を追加してからINSERTします。1つのリテラルを長くしすぎず、CLOBとして組み立てるのがポイントです。

append-clob-chunks.sql
DECLARE
  v_body CLOB;
BEGIN
  DBMS_LOB.CREATETEMPORARY(v_body, TRUE);

  DBMS_LOB.APPEND(v_body, TO_CLOB('短い断片1'));
  DBMS_LOB.APPEND(v_body, TO_CLOB('短い断片2'));
  DBMS_LOB.APPEND(v_body, TO_CLOB('短い断片3'));

  INSERT INTO message_log (id, body)
  VALUES (1, v_body);

  DBMS_LOB.FREETEMPORARY(v_body);
END;
/

DBMS_LOB の詳しい使い方は DBMS_LOB完全ガイド にまとめています。LOBセグメントの容量確認が必要な場合は LOBセグメントの確認・サイズ取得方法 も確認してください。

対処4: EMPTY_CLOBとRETURNINGで後から書き込む

DB内でCLOB列へ確実に書き込みたい場合は、まず EMPTY_CLOB() で行を作り、RETURNING でLOBロケータを受け取ってから DBMS_LOB.WRITEAPPEND で書き込む方法もあります。CLOB列への登録処理を明示できるため、長文をSQLリテラルへ埋め込む必要がありません。

empty-clob-returning.sql
DECLARE
  v_body CLOB;
  v_text VARCHAR2(32767) := '短い断片をここに入れる';
BEGIN
  INSERT INTO message_log (id, body)
  VALUES (1, EMPTY_CLOB())
  RETURNING body INTO v_body;

  DBMS_LOB.WRITEAPPEND(v_body, LENGTH(v_text), v_text);
END;
/

対処5: SQL*LoaderやData Pumpで取り込む

移行データや大量の長文データを投入するなら、INSERT文を大量生成するより、SQL*LoaderやData Pumpを使う方が安定します。長い本文やファイル由来のデータをSQLリテラルに変換する工程そのものをなくせます。

sqlldr-control-file.ctl
LOAD DATA
INFILE 'message_log.csv'
INTO TABLE message_log
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
TRAILING NULLCOLS
(
  id,
  body CHAR(1000000)
)

SQL*Loaderの基本は SQL*Loader完全ガイド、スキーマ移行や大量データ移行なら Data Pump完全ガイド が参考になります。

移行SQLを自動生成している場合の直し方

他システムから移行用INSERT文を自動生成している場合、長文列まで INSERT INTO ... VALUES ('...') にしてしまうとORA-01704が起きやすくなります。移行スクリプト側で、長文列だけCSV、LOBFILE、SQL*Loader、Data Pump、またはアプリ投入に分けるのが現実的です。

短いマスタデータ

通常のINSERT文でも運用しやすいです。文字列長が短く、差分管理したい設定データなどに向いています。

長いJSON/XML/HTML

INSERT文へ直書きせず、CLOB列へバインド投入、SQL*Loader、LOBFILE、Data Pumpを使います。

添付ファイルや巨大本文

DBに入れるならLOB設計、外部保管するならファイルストレージと参照キー設計を検討します。

MAX_STRING_SIZE=EXTENDEDで解決してよいか

MAX_STRING_SIZE=EXTENDED では、SQLのテキストリテラル上限が32767バイトになります。ただし、この変更はデータベース全体の仕様に関わるため、ORA-01704の一時回避として気軽に変更するものではありません。既存アプリ、インデックス、互換性、戻しにくさを確認する必要があります。

検討してよいケース

設計として32767バイトまでのVARCHAR2を使う明確な理由があり、環境全体で検証できる場合です。

避けたいケース

数万バイト以上のJSON、XML、HTMLを扱うだけの場合です。この場合はCLOBや外部投入方式の方が自然です。

本番変更前の確認

DBバージョン、互換性、既存アプリ、DDL、インデックス、バックアップ/リストア手順、検証環境での再現確認を行います。

ORA-01461との違い

ORA-01704 は、SQLに直接書いた文字列リテラルが長すぎるエラーです。一方、ORA-01461 は、バインドや型変換の文脈でLONG/CLOB/VARCHAR2の扱いが合わない時に出ることがあります。どちらも長い文字列で起きますが、見る場所が違います。

ORA-01704

SQL文内の引用符付き文字列が長すぎる時に発生します。まずSQLへ長文を直書きしていないか確認します。

ORA-01461

バインド変数、列型、ドライバ側の型指定、LONG/CLOBの扱いを確認します。詳細は ORA-01461の原因と解決方法 を参照してください。

ORA-12899

列に入る値が大きすぎるエラーです。リテラルの上限ではなく、列定義やBYTE/CHARセマンティクスを確認します。

実務での切り分け手順

  1. エラーが出たSQLを確認し、長い '...' を直接書いていないか見る
  2. 保存先列が VARCHAR2 なのか CLOB なのか確認する
  3. LENGTHB で文字数ではなくバイト数を確認する
  4. アプリからの投入ならバインド変数に変更する
  5. SQLファイル移行ならSQL*LoaderやData Pumpに切り替える
  6. DB内で組み立てるなら DBMS_LOB.APPEND や一時CLOBを使う
  7. TO_CLOB を使う場合は、1つのリテラル自体が長すぎないか確認する
  8. EXTENDED変更は最後に検討し、検証環境で影響を確認する

文字数とバイト数の違いは、マルチバイト文字が混ざると特に重要です。確認SQLは LENGTH / LENGTHBで文字数・バイト数を取得する方法 が参考になります。INSERT/UPDATE/DELETEの基本は INSERT・UPDATE・DELETE完全ガイド も併せて確認してください。

check-length-bytes.sql
SELECT LENGTH(long_text)  AS chars,
       LENGTHB(long_text) AS bytes
FROM source_table
WHERE id = :id;

よくある質問

CLOB列なのにORA-01704になるのはなぜですか?

CLOB列の容量ではなく、SQLに直接書いた文字列リテラルの上限に当たっているためです。CLOB列へ入れる場合でも、値はバインド変数やDBMS_LOBで渡してください。

TO_CLOBで囲めば解決しますか?

短い断片を TO_CLOB で連結する場合は有効ですが、1つの長いリテラル自体が上限を超えている場合は解決しません。リテラルを短い断片に分けるか、外部からCLOBとして渡します。

4000文字ではなく4000バイトですか?

通常はバイト数で考えます。日本語などのマルチバイト文字では、文字数が4000未満でもバイト数が上限を超えることがあります。LENGTHB で確認してください。

まとめ

ORA-01704は、SQLに直接書いた文字列リテラルが長すぎる時に発生します。CLOB列に入れるつもりでも、SQL文中の '...' が上限を超えると失敗します。

実務では、長文をSQLに埋め込まず、バインド変数、CLOB、一時LOB、SQL*Loader、Data Pumpで渡す形に変えるのが安全です。特にJSON、XML、HTML、メール本文、ログ本文を扱う処理では、最初からCLOB前提で設計しておくと再発を防げます。

参考