【Oracle】ORA-01036の原因と解決方法|illegal variable name/number・バインド変数名/番号が不正です

【Oracle】ORA-01036の原因と解決方法|illegal variable name/number・バインド変数名/番号が不正です Oracle

ORA-01036: illegal variable name/number は、Oracleでバインド変数の名前や番号の扱いが不正なときに発生するエラーです。新しいエラーメッセージでは、SQL文内に存在しないバインド変数名をバインドしようとした場合に unrecognized bind variable と表示されることもあります。

よくある原因は、SQL側の :dept_id とアプリ側のパラメータ名が一致していない、値ではなく列名や表名を :table_name のようにバインドしようとしている、DDLでバインドできない場所にプレースホルダを書いている、位置指定バインドの数や順番がずれている、といったパターンです。

先に結論
ORA-01036は、SQL本文に実際に存在するプレースホルダだけを、実行方式に合った名前または位置でバインドすると解消できます。列名・表名・DDLのオブジェクト名は通常バインドできません。値はバインド変数、SQL構造は許可リストで組み立てる、と分けて考えます。
スポンサーリンク

ORA-01036とは

Oracle公式のエラー説明では、ORA-01036はバインド呼び出しへ渡された変数に、SQL文内の対応するプレースホルダがない場合、またはユーザー側でバインドコンテキストを見つけられない場合に発生するとされています。つまり、SQL本文とバインド指定の対応が崩れている状態です。

原因 よくある例 修正の方向
SQLにない名前を渡している SQLは :dept_id、アプリは department_id プレースホルダ名を一致させる
列名や表名をバインドしている TRUNCATE TABLE :table_name オブジェクト名は許可リストで文字列化する
DDLで値以外をバインドしている ALTER USER :user IDENTIFIED BY :password DDLは安全に文字列を組み立てる
IN句のリストを1つの値で渡している IN (:ids) に配列やCSVを渡す 個数分のプレースホルダを作る
位置指定の数や順番が違う :1:2 と値配列が合わない 出現位置と値を照合する
ドライバの書き方が違う @id? をOracle向けSQLへ混ぜる 利用ドライバのプレースホルダ形式に合わせる

ORA-01008との違い

ORA-01008ORA-01036 はどちらもバインド変数まわりのエラーですが、見る場所が違います。ORA-01008はSQL内にあるプレースホルダへ値が足りない状態、ORA-01036はSQL内にない名前を渡したり、バインドできない場所へプレースホルダを書いた状態です。

エラー 主な意味
ORA-01008 SQL内のバインド変数に値が不足している :dept_id があるのに値を渡していない
ORA-01036 バインド変数名や番号の指定がSQLと合っていない SQLにない :department_id を渡している

値の渡し忘れが疑わしい場合は ORA-01008の原因と解決方法 も確認してください。

SQLにないバインド名を渡しているケース

SQL本文に書いたプレースホルダ名と、アプリ側で追加したパラメータ名が違うとORA-01036になります。リファクタリングでSQLだけ変更した、パラメータ名だけ変更した、という場合に起きやすいです。

bind-name-mismatch.sql
-- SQLには :dept_id がある
SELECT *
FROM employees
WHERE department_id = :dept_id;

-- NG: アプリ側では別名 department_id を渡している
-- params = { department_id: 10 }

-- OK: SQL内の名前と同じ名前で渡す
-- params = { dept_id: 10 }

列名や表名をバインドしようとしているケース

バインド変数で置き換えられるのは、基本的に値です。テーブル名、列名、ORDER BYの列名、DDLのオブジェクト名など、SQL構造そのものは通常バインドできません。この誤解はORA-01036のかなり多い原因です。

object-name-cannot-be-bound.sql
-- NG: テーブル名はバインド変数にできない
TRUNCATE TABLE :table_name;

-- NG: 列名も値のようにはバインドできない
SELECT :column_name
FROM employees
WHERE employee_id = :employee_id;

-- OK: オブジェクト名は許可リストで選び、値だけをバインドする
SELECT employee_name
FROM employees
WHERE employee_id = :employee_id;

バインド変数の基本は Oracleのバインド変数完全ガイド も参考になります。

ALTER USERやCREATE USERで発生するケース

ALTER USERCREATE USER のユーザー名、パスワード、表領域名などを、値のようにバインドしようとしてORA-01036になることがあります。DDLのオブジェクト名はバインドできないため、許可した値だけを安全に文字列へ組み立てます。

ddl-object-name-bind.sql
-- NG: DDLのユーザー名やパスワードを値のようにバインドしようとしている
ALTER USER :user_name IDENTIFIED BY :password;

-- OKの考え方: ユーザー名は許可リストやDBMS_ASSERTで検証してから組み立てる
-- v_sql := 'ALTER USER ' || safe_user_name || ' IDENTIFIED BY "' || safe_password || '"';
-- EXECUTE IMMEDIATE v_sql;

ORDER BYの列名をバインドしているケース

一覧画面で並び替え列を変えたいときに、ORDER BY :sort_column のように書くと、列名ではなく値として扱われます。期待どおりに並ばないだけでなく、ドライバや実行方法によってはORA-01036の原因になります。

order-by-column-bind.sql
-- NG: 列名はバインド変数にしない
SELECT employee_id, employee_name, hire_date
FROM employees
ORDER BY :sort_column;

-- OKの考え方: sort_column は employee_id / employee_name / hire_date だけ許可する
SELECT employee_id, employee_name, hire_date
FROM employees
ORDER BY hire_date;

IN句のリストを1つのバインドで渡しているケース

IN (:ids) に配列やカンマ区切り文字列を1つ渡して、複数値として扱わせようとする実装もよくあります。Oracleでは、単純なバインド変数1つが自動で複数のプレースホルダへ展開されるわけではありません。ドライバや実装によってはORA-01036や別のバインドエラーにつながります。

in-list-bind-placeholders.sql
-- NGの考え方: :ids 1つに [10, 20, 30] を渡して複数値にしたい
SELECT *
FROM employees
WHERE department_id IN (:ids);

-- OK: 値の個数分だけプレースホルダを作る
SELECT *
FROM employees
WHERE department_id IN (:id1, :id2, :id3);

-- params = { id1: 10, id2: 20, id3: 30 }

件数が多い場合は、一時表、コレクション、JSON_TABLEなど別の渡し方も検討します。IN句の基本と注意点は OracleのIN句の使い方 も参考になります。

位置指定バインドの番号や順番がずれているケース

:1:2 のような位置指定では、SQL内の出現位置と値の配列を対応させます。番号の見た目だけに頼ると、ドライバ側の扱いとずれてORA-01036やORA-01008につながります。

positional-bind-number.sql
-- SQLには2つの位置指定バインドがある
SELECT *
FROM employees
WHERE department_id = :1
  AND status = :2;

-- OK: 出現位置に合わせて2つ渡す
-- values = [10, 'ACTIVE']

JDBC・ODP.NET・Pythonで名前の指定方法が違うケース

アプリのドライバによって、パラメータ名にコロンを含めるか、含めないか、位置指定か名前指定かが異なります。SQL側はOracleの :name 形式でも、APIに渡す名前は name にする、という実装もあります。ODP.NETでは BindByName の設定によって、名前で対応するか位置で対応するかが変わる点にも注意します。

driver-parameter-name.txt
-- SQL側
SELECT * FROM employees WHERE employee_id = :employee_id

-- NG例: ドライバの想定と違う名前を渡している
-- ParameterName = ':employee_id' なのか 'employee_id' なのかを混同

-- OK: 利用しているドライバの仕様に合わせる
-- SQL内のプレースホルダ名と、APIに渡すパラメータ名の対応をログで確認する

SQL ServerやMySQLのプレースホルダ形式を混ぜているケース

SQL Serverの @id、ODBCの ?、MySQL系のプレースホルダをOracle向けSQLへ混ぜると、ドライバや実行方法によってバインドとして認識されません。Oracle向けには、利用ドライバが期待する形式へ合わせます。

wrong-placeholder-style.sql
-- NG: SQL Server風の @id をOracle SQLへそのまま混ぜている
SELECT *
FROM employees
WHERE employee_id = @employee_id;

-- OK: Oracleの名前付きバインドに合わせる
SELECT *
FROM employees
WHERE employee_id = :employee_id;

無効な文字として落ちる場合は ORA-00911の原因と解決方法 も確認してください。

EXECUTE IMMEDIATEのUSING句で発生するケース

PL/SQLの EXECUTE IMMEDIATE では、動的SQL内のプレースホルダと USING 句の値を対応させます。SQL内にない名前を渡す、またはSQL構造をバインドしようとするとORA-01036の原因になります。

execute-immediate-ora01036.sql
-- NG: テーブル名をバインドしようとしている
DECLARE
    v_sql VARCHAR2(1000);
    v_count NUMBER;
BEGIN
    v_sql := 'SELECT COUNT(*) FROM :table_name WHERE employee_id = :employee_id';
    EXECUTE IMMEDIATE v_sql INTO v_count USING 'EMPLOYEES', 100;
END;
/

-- OK: テーブル名は検証済み文字列、値だけをバインドする
DECLARE
    v_sql VARCHAR2(1000);
    v_count NUMBER;
BEGIN
    v_sql := 'SELECT COUNT(*) FROM employees WHERE employee_id = :employee_id';
    EXECUTE IMMEDIATE v_sql INTO v_count USING 100;
END;
/

EXECUTE IMMEDIATE の基本は OracleのEXECUTE IMMEDIATE完全ガイド も参考になります。

動的SQLの完成形をログで確認する

ORA-01036は、ソースコードの断片だけ見ても原因が分かりにくいことがあります。条件分岐や文字列連結が終わった後のSQL全文と、渡しているバインド名・値の一覧を同じログに出すと切り分けやすくなります。

確認項目 見る内容
SQL本文 :employee_id など実際に出ているプレースホルダ
バインド名 SQL本文に存在する名前だけを渡しているか
バインド値 値の数、NULL、型が意図どおりか
SQL構造 列名・表名・ORDER BY列を値のようにバインドしていないか

ORA-00933・ORA-00936との違い

ORA-01036 はバインド変数の指定が主な原因です。一方、ORA-00933 は句の終わり方や他DB構文、ORA-00936 は式不足が中心です。

エラー 主な原因 確認する場所
ORA-01036 バインド名、番号、バインドできない場所 SQL内の :name とアプリのパラメータ
ORA-01008 バインド値の不足 SQL内のプレースホルダ数と値の数
ORA-00933 句の順序、SQLの終わり方、他DB構文 LIMITUPDATE JOIN、余分な句
ORA-00936 式、列、値、条件の不足 SELECT 句、WHERE 句、関数引数

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

修正チェックリスト

手順 確認すること 見るポイント
1 SQL本文をログに出す 実際に :name が存在するか
2 渡しているパラメータ名を見る SQLにない名前を渡していないか
3 列名・表名をバインドしていないか確認する 値とSQL構造を分けているか
4 DDLを確認する ユーザー名、表名、列名を値のようにバインドしていないか
5 IN句のリストを見る IN (:ids) に配列やCSVを1個で渡していないか
6 位置指定の数と順番を見る :1:2 と値配列が合っているか
7 ドライバの記法を確認する コロン付き/なし、名前指定/位置指定を混同していないか
8 ORA-01008と切り分ける 不足なのか、不正な名前・場所なのかを分ける

よくある質問

ORA-01036とORA-01008は何が違いますか?

ORA-01008はSQL内のプレースホルダに値が不足している状態です。ORA-01036は、SQL内にない名前を渡したり、列名や表名などバインドできない場所へプレースホルダを書いた状態です。

テーブル名をバインド変数にできますか?

通常はできません。テーブル名や列名はSQL構造なので、許可リストで検証して文字列として組み立てます。データ値だけをバインド変数にします。

ParameterNameにはコロンを付けますか?

利用するドライバやAPIによります。SQL本文では :employee_id と書き、API側では employee_id を指定する実装もあります。ドライバ仕様と実際のログで確認します。

IN句に配列を1つ渡せますか?

単純な IN (:ids) に配列やカンマ区切り文字列を渡しても、自動で複数値へ展開されるわけではありません。値の個数分だけ :id1, :id2, :id3 のようにプレースホルダを作るか、別の渡し方を検討します。

ALTER USERでORA-01036が出るのはなぜですか?

ユーザー名やパスワードなどDDLの構造部分を値のようにバインドしようとしている可能性があります。オブジェクト名は検証済み文字列として組み立て、値バインドとは分けます。

まとめ

ORA-01036は、バインド変数名や番号、またはバインドする場所がSQL本文と合っていないときに発生します。まず完成したSQLと、渡しているパラメータ名・値の一覧を並べて確認しましょう。

値はバインド変数で渡し、列名・表名・DDLのオブジェクト名は許可リストで検証してからSQLへ組み込みます。ORA-01008との違いを押さえると、値不足なのか、名前や場所の不正なのかを切り分けやすくなります。

参考

ORA-01036 – Oracle Database Error Help

Native Dynamic SQL – Oracle Database PL/SQL Language Reference

Database Object Names and Qualifiers – Oracle SQL Language Reference