【Oracle】ORA-01008の原因と解決方法|not all variables bound・バインド変数不足の直し方

【Oracle】ORA-01008の原因と解決方法|not all variables bound・バインド変数不足の直し方 Oracle

ORA-01008: not all variables bound は、SQLやPL/SQLに書いたバインド変数プレースホルダに、実行時の値が渡されていないときに発生するOracleエラーです。たとえば :employee_id:1 をSQLに書いたのに、アプリ側や USING 句で値を渡していない状態です。

よくある原因は、バインド変数の渡し忘れ、名前の不一致、位置指定バインドの順番ずれ、同じプレースホルダを複数回使ったときの数え方の誤解、動的SQLで条件だけ追加して値を追加していないケースです。

先に結論
ORA-01008は、SQL本文に出てくるプレースホルダと、実行時に渡している値の対応をそろえると解消できます。まずSQL内の : 付き変数を数え、名前指定か位置指定か、同じ名前を繰り返した場合の扱いが実行方式に合っているかを確認します。
スポンサーリンク

ORA-01008とは

Oracle公式のエラー説明では、ORA-01008はバインド変数プレースホルダを含むSQLまたはPL/SQL文を実行した際、そのプレースホルダに値が提供されていない場合に発生するとされています。新しい説明では、値がないプレースホルダ名または位置が示されることもあります。

原因 修正の方向
値の渡し忘れ WHERE employee_id = :employee_id なのに値を渡していない 全プレースホルダに値を渡す
名前の不一致 :dept_id に対して department_id を渡している SQL側とアプリ側の名前を合わせる
位置指定の不足 :1:2 の一部だけ渡している 位置の数だけ値を渡す
SQL Developer / SQL*Plusでの未指定 :dept_id に値を入力せず実行している 実行時に値を入力する、または変数を定義する
重複プレースホルダの誤解 同じ :x を複数回使う 名前指定か位置指定かで数え方を変える
動的SQLの条件追加漏れ SQLには条件を足したが、値リストに追加していない SQL生成とバインド値生成を同じ分岐で扱う

まずSQL内のバインド変数を洗い出す

最初に、実際にDBへ送っているSQL全文をログに出し、: で始まるプレースホルダを確認します。ソースコード上の断片ではなく、条件分岐や文字列連結が終わった後のSQLを見るのが大切です。

find-placeholders.sql
-- SQL本文に2つのプレースホルダがある
SELECT *
FROM employees
WHERE department_id = :dept_id
  AND status = :status;

-- 実行時には :dept_id と :status の両方に値が必要

バインド変数の基本や性能面の意味は Oracleのバインド変数完全ガイド も参考になります。

SQL DeveloperやSQL*Plusで発生するケース

SQL Developer、SQLcl、SQL*Plusで :dept_id のようなバインド変数を含むSQLを実行する場合、実行時に値を入力するか、事前に変数を定義して値を入れる必要があります。単にSQLを貼り付けただけで値を渡していないと、ORA-01008の原因になります。

sql-developer-sqlplus-bind.sql
-- SQL Developerでは実行時に :dept_id と :status の値を入力する
SELECT *
FROM employees
WHERE department_id = :dept_id
  AND status = :status;

-- SQL*Plus / SQLclでは事前にバインド変数を定義して値を入れる
VARIABLE dept_id NUMBER
VARIABLE status VARCHAR2(20)
EXEC :dept_id := 10
EXEC :status := 'ACTIVE'

SELECT *
FROM employees
WHERE department_id = :dept_id
  AND status = :status;

:dept_id はバインド変数、&dept_id はSQL*Plus系の置換変数です。見た目は似ていますが、仕組みが違うため混同しないようにします。

名前指定バインドで値を渡し忘れているケース

名前指定バインドでは、SQL内のプレースホルダ名と、実行時に渡すパラメータ名を一致させます。名前が似ていても、1文字違えば別の変数として扱われます。

named-bind-missing.sql
-- SQLには :dept_id と :status がある
SELECT *
FROM employees
WHERE department_id = :dept_id
  AND status = :status;

-- NG: :status に値を渡していない
-- params = { dept_id: 10 }

-- OK: SQL内の名前と同じ名前で値を渡す
-- params = { dept_id: 10, status: 'ACTIVE' }

位置指定バインドで数が不足しているケース

:1:2 のような位置指定では、プレースホルダの位置に対応する値を順番に渡します。名前ではなく出現位置で対応するため、値の数と順番が重要です。

positional-bind-missing.sql
-- SQLには位置指定プレースホルダが2つある
SELECT *
FROM employees
WHERE department_id = :1
  AND status = :2;

-- NG: 1つしか渡していない
-- values = [10]

-- OK: :1, :2 の順番で2つ渡す
-- values = [10, 'ACTIVE']

同じバインド名を複数回使う場合の注意点

ORA-01008で特に混乱しやすいのが、同じ名前のプレースホルダを複数回使うケースです。Oracle公式ドキュメントでは、名前指定なら同じ名前には1つの値でよい一方、位置指定では同じ名前に見えても位置ごとに値が必要な場合があると説明されています。

実行方式 同じ <code>:x</code> を複数回書いた場合 考え方
名前指定バインド 同じ名前に1つの値を対応させる :x は同じ値として扱う
位置指定バインド 出現位置ごとに値が必要 同じ :x に見えても位置で数える
PL/SQLの EXECUTE IMMEDIATE SQL文かPL/SQLブロックかで扱いが変わる 下の例のように文の種類を確認する
repeated-placeholder.sql
-- 動的SQLが通常のSQL文の場合、出現位置ごとにUSINGへ値を渡す
DECLARE
    v_sql VARCHAR2(200);
    v_count NUMBER;
BEGIN
    v_sql := 'SELECT COUNT(*) FROM employees WHERE department_id = :x OR manager_id = :x';

    -- NG: :x が2回出ているのに1つしか渡していない
    -- EXECUTE IMMEDIATE v_sql INTO v_count USING 10;

    -- OK: 出現位置ごとに渡す
    EXECUTE IMMEDIATE v_sql INTO v_count USING 10, 10;
END;
/

EXECUTE IMMEDIATEのUSING句が不足しているケース

PL/SQLの EXECUTE IMMEDIATE では、動的SQL内のプレースホルダに対応する値を USING 句へ渡します。SQLに条件を追加したのに USING 側を増やしていないと、ORA-01008になります。

execute-immediate-using.sql
-- NG: :status に対応するUSING値がない
DECLARE
    v_sql VARCHAR2(1000);
    v_count NUMBER;
BEGIN
    v_sql := 'SELECT COUNT(*) FROM employees WHERE department_id = :dept_id AND status = :status';
    EXECUTE IMMEDIATE v_sql INTO v_count USING 10;
END;
/

-- OK: SQL内のプレースホルダ数に合わせる
DECLARE
    v_sql VARCHAR2(1000);
    v_count NUMBER;
BEGIN
    v_sql := 'SELECT COUNT(*) FROM employees WHERE department_id = :dept_id AND status = :status';
    EXECUTE IMMEDIATE v_sql INTO v_count USING 10, 'ACTIVE';
END;
/

EXECUTE IMMEDIATE の基本は OracleのEXECUTE IMMEDIATE完全ガイド、列数やバインド数が実行時まで分からない場合は DBMS_SQL完全ガイド も参考になります。

RETURNING INTOとUSINGの置き場所を間違えるケース

動的DMLで RETURNING INTO を使う場合、入力用の値は USING、戻り値は RETURNING INTO に分けます。戻り値用の変数まで USING へ入れたり、逆に入力値を入れ忘れたりすると切り分けが難しくなります。

returning-into-bind.sql
-- OK: 入力値はUSING、戻り値はRETURNING INTO
DECLARE
    v_sql VARCHAR2(1000);
    v_employee_id NUMBER;
BEGIN
    v_sql := 'INSERT INTO employees_work (employee_name, status) VALUES (:name, :status) RETURNING employee_id INTO :id';

    EXECUTE IMMEDIATE v_sql
        USING 'Sato', 'ACTIVE'
        RETURNING INTO v_employee_id;
END;
/

INSERT、UPDATE、DELETEなどDMLの基本は OracleのINSERT・UPDATE・DELETE完全ガイド も参考になります。

NULLを渡したい場合の注意点

PL/SQLのネイティブ動的SQLでは、USING NULL のようにリテラルの NULL を直接渡せない場面があります。NULLを渡したい場合は、未初期化の変数を用意して、それを USING に渡します。

using-null-variable.sql
DECLARE
    v_sql VARCHAR2(1000);
    v_null VARCHAR2(1);
BEGIN
    v_sql := 'UPDATE employees_work SET memo = :memo WHERE employee_id = :employee_id';

    -- v_null は未初期化なのでNULLとして扱われる
    EXECUTE IMMEDIATE v_sql USING v_null, 100;
END;
/

動的SQLで条件だけ追加して値を追加していないケース

検索画面のように任意条件を追加するSQLでは、SQL文字列とバインド値の配列を別々に組み立てるとズレやすくなります。条件を追加する分岐と、対応するバインド値を追加する分岐を同じ場所にまとめると安全です。

optional-condition-bind.sql
-- NGの考え方: SQLには条件を足したが、値を足していない
SELECT *
FROM employees
WHERE 1 = 1
  AND department_id = :dept_id
  AND status = :status;

-- params = { dept_id: 10 }  -- :status が不足

-- OK: 条件と値をセットで追加する
-- if status is not null:
--     sql += ' AND status = :status'
--     params['status'] = status

列名やテーブル名はバインドできない

バインド変数で置き換えられるのは値です。列名、テーブル名、ORDER BYの列名など、SQL構造そのものは通常バインドできません。列名を :column_name のように書くと、値として扱われるため、期待したSQLにならないことがあります。

object-name-is-not-bind-value.sql
-- NG: 列名をバインド変数として選択しようとしている
SELECT :column_name
FROM employees
WHERE employee_id = :employee_id;

-- OK: 列名はホワイトリストで選び、値だけをバインドする
SELECT employee_name
FROM employees
WHERE employee_id = :employee_id;

SQL構造を動的に変える場合は、許可した列名だけを文字列として組み立て、値はバインド変数にします。この切り分けはSQLインジェクション対策としても重要です。

JDBCやPythonで発生するケース

アプリケーションから実行する場合も、SQL内のプレースホルダと渡すパラメータの数・名前を合わせる点は同じです。フレームワークによって名前の指定方法や配列の扱いが違うため、最終的にDBへ送っているSQLとパラメータをログで確認します。

application-bind-example.txt
-- NG: SQLには2つの値が必要だが、1つしか渡していない
sql = "SELECT * FROM employees WHERE department_id = :dept_id AND status = :status"
params = { "dept_id": 10 }

-- OK: すべてのプレースホルダに値を渡す
params = { "dept_id": 10, "status": "ACTIVE" }

-- JDBCなど位置指定APIの場合は、番号の抜けにも注意
preparedStatement.setInt(1, 10)
preparedStatement.setString(2, "ACTIVE")

ORA-00936やORA-00933との違い

ORA-01008 はSQLの構文自体より、実行時に渡すバインド値の不足が原因です。一方、ORA-00936 は式不足、ORA-00933 は句の終わり方やOracle非対応構文が主な原因です。

エラー 主な原因 確認する場所
ORA-01008 バインド変数に値が渡されていない SQL内の :name と実行時パラメータ
ORA-00936 式、列、値、条件が抜けている SELECT 句、WHERE 句、関数引数
ORA-00933 句の順序、末尾、他DB構文が合わない LIMITUPDATE JOIN、余分な句

式不足が疑わしい場合は ORA-00936の原因と解決方法、句の終わり方が疑わしい場合は ORA-00933の原因と解決方法 を確認してください。

修正チェックリスト

手順 確認すること 見るポイント
1 実行SQLをログに出す ソース断片ではなく完成したSQLを見る
2 プレースホルダを数える :dept_id:1 などをすべて洗い出す
3 名前指定か位置指定か確認する 名前で対応するのか、出現位置で対応するのかを確認する
4 SQL DeveloperやSQL*Plusの実行方法を見る 実行時入力、VARIABLEEXEC の有無を確認する
5 重複プレースホルダを見る 同じ名前を複数回使った場合の扱いを確認する
6 EXECUTE IMMEDIATE の句を確認する USINGINTORETURNING INTO を分ける
7 任意条件の分岐を見る 条件追加とバインド値追加が同じ分岐にあるか確認する
8 列名や表名をバインドしていないか確認する SQL構造はホワイトリスト、値はバインドに分ける

よくある質問

ORA-01008はSQLの構文エラーですか?

SQL本文にプレースホルダがあるのに値が渡されていない実行時エラーです。ただし、動的SQLの組み立てミスが原因で結果的に発生することもあります。

同じバインド変数名を2回書いたら値も2回必要ですか?

実行方式によります。名前指定バインドなら1つの名前に1つの値でよいことが多いですが、PL/SQLの通常の動的SQLでは出現位置ごとに USING の値が必要になるケースがあります。

NULLを渡したいときはどうしますか?

PL/SQLの動的SQLでは、USING NULL ではなく、未初期化の変数を用意して渡す方法が使えます。アプリ側では利用しているドライバのNULL指定方法に従います。

SQL Developerで実行するときも値の指定が必要ですか?

必要です。:dept_id のようなバインド変数を含むSQLは、実行時に値を入力するか、SQL*Plus系なら VARIABLEEXEC で事前に値を設定します。

どの変数が不足しているか分からない場合は?

完成したSQLとパラメータ一覧を同じログに出します。SQL内の : 付きプレースホルダ一覧と、渡している名前・位置を表にすると見つけやすいです。

まとめ

ORA-01008は、SQL内のバインド変数に値が渡されていないときに発生します。まず完成したSQLを確認し、プレースホルダの数・名前・出現位置と、実行時に渡す値を照合しましょう。

名前指定と位置指定、通常の動的SQLとPL/SQLブロック、USINGRETURNING INTO の違いを整理すると、原因をかなり早く絞り込めます。動的SQLでは、条件追加とバインド値追加をセットで扱うのが安全です。

参考

ORA-01008 – Oracle Database Error Help

Native Dynamic SQL – Oracle Database PL/SQL Language Reference

PL/SQL Dynamic SQL – Oracle Database PL/SQL Language Reference