【Oracle】ORA-01007の原因と解決方法|variable not in select list・SELECTリストと受け取り変数の直し方

【Oracle】ORA-01007の原因と解決方法|variable not in select list・SELECTリストと受け取り変数の直し方 Oracle

ORA-01007: variable not in select list は、OracleでSELECT句に存在しない列や変数を受け取ろうとした時に発生するエラーです。典型例は、SELECT で1列しか返していないのに INTOFETCH INTO で2つ以上の変数を指定しているケースです。動的SQLや DBMS_SQL でSELECTリストが変わる処理でも起きやすいです。

Oracle公式の説明では、SELECT句に列挙されていない変数を参照したことが原因です。古い形式の説明では、OCIの DESCRIBENAMEDEFINE でSELECT句の変数数を超えた位置を参照した場合や、SELECT句より多い INTO 変数を指定した場合にも発生するとされています。

先に結論
ORA-01007が出たら、まず実際に実行している SELECT の列数と、受け取り側の変数数を合わせます。1列なら受け取りも1つ、3列なら受け取りも3つです。カーソルや動的SQLでは、最終的なSELECTリストをログに出して確認します。
スポンサーリンク

ORA-01007とは

ORA-01007は、SQL構文そのものよりも「返す列」と「受け取る変数」の対応が崩れている時に出ます。似たエラーに ORA-01006ORA-01008ORA-01036 がありますが、ORA-01007はSELECTリスト側の問題として見るのが近道です。

確認点 よくある原因 見る場所
SELECT列数 受け取り変数より少ない SELECT
INTO句 列数より変数数が多い SELECT ... INTO
FETCH INTO カーソルの列数と変数数が違う FETCH cursor INTO ...
動的SQL 条件や分岐でSELECTリストが変わる 完成後のSQLログ
DBMS_SQL 存在しない列位置をDEFINEしている DEFINE_COLUMN の位置

SELECT INTO 周辺の例外は、ORA-01403・ORA-01422のSELECT INTOガイド も参考になります。ORA-01007は行数ではなく、列数と受け取り変数の対応を見る点が違います。

なお、静的PL/SQLの SELECT ... INTOFETCH INTO では、環境や書き方によってコンパイル時の列数不一致として検出されることもあります。ORA-01007として表面化しやすいのは、動的SQL、OCI系の取得処理、SQL*Forms/Reports系、DBMS_SQL など、SELECTリストを実行時に扱う処理です。

ORA-01006・ORA-01008・ORA-01036との違い

バインド変数系の記事から来た場合は、まず見る場所を分けます。ORA-01007は、バインド値ではなくSELECTリストと受け取り先の不一致です。

ORA-01007

主な意味: SELECTリストにない列・位置を受け取ろうとしている

例: 1列SELECTなのに INTO v1, v2 としている

ORA-01006

主な意味: SQLに存在しないバインド変数を渡している

関連記事: ORA-01006

ORA-01008

主な意味: SQL内のバインド変数に値が不足している

関連記事: ORA-01008

ORA-01036

主な意味: バインド変数名や番号、バインドできない場所の指定が不正

関連記事: ORA-01036

SELECT列数とINTO変数数が合っていない

PL/SQLの SELECT ... INTO では、SELECT句の列数と INTO 句の変数数を一致させます。列数より多く受け取ろうとすると、SELECTリストに存在しない変数を参照する形になります。

select-into-too-many-targets.sql
DECLARE
  v_emp_id   employees.employee_id%TYPE;
  v_emp_name employees.employee_name%TYPE;
BEGIN
  -- NG: SELECTは1列、INTOは2変数
  SELECT employee_id
  INTO v_emp_id, v_emp_name
  FROM employees
  WHERE employee_id = 100;
END;
/
select-into-fixed.sql
DECLARE
  v_emp_id   employees.employee_id%TYPE;
  v_emp_name employees.employee_name%TYPE;
BEGIN
  SELECT employee_id, employee_name
  INTO v_emp_id, v_emp_name
  FROM employees
  WHERE employee_id = 100;
END;
/

逆に、SELECTは2列なのに受け取りが1変数だけなら、別の列数不一致エラーになります。値の個数が多い/少ない方向の切り分けでは ORA-00913 も参考になります。いずれにしても、SELECT句とINTO句を横に並べて数えるのが基本です。

カーソルFETCHで受け取り変数が合っていない

明示カーソルやREF CURSORの FETCH INTO でも同じです。カーソルが返す列数と、FETCH INTO の変数数を一致させます。

fetch-into-variable-count-mismatch.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;

  v_emp_id   employees.employee_id%TYPE;
  v_emp_name employees.employee_name%TYPE;
BEGIN
  OPEN c_emp;
  -- NG: カーソルは1列、受け取りは2変数
  FETCH c_emp INTO v_emp_id, v_emp_name;
  CLOSE c_emp;
END;
/

フェッチ順やカーソル状態が絡む場合は、ORA-01002ORA-01001 と合わせて、カーソルの開閉・COMMIT・ループ位置も確認します。

SELECT * とテーブル変更で列数がずれる

SELECT * を使っていると、テーブルやビューの列追加・削除で受け取り側と列数がずれます。プログラムから受け取る処理では、必要な列を明示する方が安全です。

avoid-select-star-for-fetch.sql
-- NG: テーブル定義変更で列数が変わる
SELECT *
FROM employees;

-- OK: 受け取る列を明示する
SELECT employee_id,
       employee_name,
       department_id
FROM employees;

ビューを経由している場合も、ビュー定義の変更でSELECTリストが変わります。本番だけで発生する場合は、ローカルと本番でテーブル・ビュー定義が一致しているかも確認します。

動的SQLでSELECTリストが変わっている

動的SQLでは、条件分岐によってSELECTリストが変わるのに、受け取り側を固定しているとORA-01007につながります。実際に実行しているSQL文字列をログに出し、列数を確認します。

dynamic-select-list-mismatch.sql
DECLARE
  v_sql      VARCHAR2(4000);
  v_id       NUMBER;
  v_name     VARCHAR2(100);
  v_detailed BOOLEAN := FALSE;
BEGIN
  IF v_detailed THEN
    v_sql := 'SELECT employee_id, employee_name FROM employees WHERE employee_id = :id';
  ELSE
    v_sql := 'SELECT employee_id FROM employees WHERE employee_id = :id';
  END IF;

  -- NG: v_detailed = FALSE の時は1列しか返らない
  EXECUTE IMMEDIATE v_sql INTO v_id, v_name USING 100;
END;
/

動的SQLの設計では、SELECTリストを固定するか、分岐ごとに受け取り変数を変えます。動的SQLの基本構文は EXECUTE IMMEDIATEガイド、バインド変数まわりの切り分けは ORA-01006 も参考になります。

DBMS_SQL.DEFINE_COLUMNの位置がSELECT列数を超えている

DBMS_SQL を使う場合は、DEFINE_COLUMN の列位置がSELECTリストの範囲内である必要があります。SELECTが2列なのに3列目を定義しようとすると、SELECTリストにない位置を参照することになります。

dbms-sql-define-column-over-select-list.sql
DECLARE
  c       INTEGER;
  n       INTEGER;
  v_id    NUMBER;
  v_name  VARCHAR2(100);
  v_dummy VARCHAR2(100);
BEGIN
  c := DBMS_SQL.OPEN_CURSOR;
  DBMS_SQL.PARSE(c,
    'SELECT employee_id, employee_name FROM employees',
    DBMS_SQL.NATIVE);

  DBMS_SQL.DEFINE_COLUMN(c, 1, v_id);
  DBMS_SQL.DEFINE_COLUMN(c, 2, v_name, 100);
  -- NG: SELECTは2列なので3列目は存在しない
  DBMS_SQL.DEFINE_COLUMN(c, 3, v_dummy, 100);

  n := DBMS_SQL.EXECUTE(c);
  DBMS_SQL.CLOSE_CURSOR(c);
END;
/

列数が固定できないSQLを DBMS_SQL で扱う場合は、先に DESCRIBE_COLUMNS で列数を取得してから、その範囲内だけ DEFINE_COLUMNCOLUMN_VALUE を呼びます。DBMS_SQLの全体像は DBMS_SQL完全ガイド も参考になります。

dbms-sql-describe-columns-before-define.sql
DECLARE
  c          INTEGER;
  col_count  INTEGER;
  desc_tab   DBMS_SQL.DESC_TAB;
  v_value    VARCHAR2(4000);
BEGIN
  c := DBMS_SQL.OPEN_CURSOR;
  DBMS_SQL.PARSE(c,
    'SELECT employee_id, employee_name FROM employees',
    DBMS_SQL.NATIVE);

  DBMS_SQL.DESCRIBE_COLUMNS(c, col_count, desc_tab);

  -- col_countの範囲内だけDEFINE_COLUMNする
  FOR i IN 1 .. col_count LOOP
    DBMS_SQL.DEFINE_COLUMN(c, i, v_value, 4000);
  END LOOP;

  DBMS_SQL.CLOSE_CURSOR(c);
END;
/

JDBCやPythonなどアプリ側で起きる場合

アプリ側では、取得している列番号や列名がSELECTリストに存在するか確認します。SQLを変更したのに、取得処理だけ古いままだと、存在しない列位置や別名を参照して失敗します。

python-fetch-column-mismatch.py
sql = """
SELECT employee_id
FROM employees
WHERE employee_id = :id
"""

cursor.execute(sql, {"id": 100})
row = cursor.fetchone()

# NG: SELECTは1列だけなので row[1] は存在しない
employee_name = row[1]

# OK: 取得したい列をSELECTに追加する
# SELECT employee_id, employee_name ...
jdbc-resultset-column-mismatch.java
String sql = "SELECT employee_id FROM employees WHERE employee_id = ?";
ResultSet rs = stmt.executeQuery(sql);

if (rs.next()) {
    int id = rs.getInt("employee_id");
    // NG: SELECTに employee_name がない
    String name = rs.getString("employee_name");
}

調査手順

ORA-01007は、実際に返る列と受け取り先を照合すると原因を絞れます。動的SQLやアプリ経由では、ソースコード上の断片ではなく、実行直前のSQLを見るのが大切です。

順番 確認すること 見るポイント
1 実行直前のSELECT文を確認する SELECTリストの列数と別名を見る
2 INTO/FETCH/DEFINE側を数える 受け取り変数数や列位置が合うか見る
3 SELECT *をやめる 必要列を明示し、変更影響を減らす
4 動的SQLの分岐を見る SELECTリストと受け取り側が同じ分岐で変わるか見る
5 アプリ側の列取得を見る 列番号・列名・別名の指定ミスを確認する

チェックリスト

  • SELECT句の列数を確認した
  • INTO句の変数数がSELECT列数と一致している
  • FETCH INTOの変数数がカーソルの列数と一致している
  • SELECT *ではなく必要な列を明示している
  • 動的SQLの最終SELECTリストをログで確認した
  • DBMS_SQL.DEFINE_COLUMNの列位置がSELECT列数内に収まっている
  • アプリ側の列番号・列名・別名がSELECTリストと一致している

よくある質問

ORA-01007は行が0件の時に出ますか?

主原因は行数ではなく列数や列位置の不一致です。行が0件の SELECT INTO なら通常は NO_DATA_FOUND、つまり ORA-01403 側を確認します。

SELECT * を使ってもよいですか?

アプリやPL/SQLで固定の変数へ受ける処理では避けた方が安全です。テーブルやビューの列追加で列数が変わるため、必要な列を明示してください。

列名ではなく列番号で取得してもよいですか?

列番号はSELECTリストの順番変更に弱いです。アプリ側では、可能なら列名や別名を明示し、SQL側も安定した別名を付けると保守しやすくなります。

まとめ

ORA-01007は、SELECTリストと受け取り側の対応が崩れている時に発生します。SELECT列数、INTO 変数数、FETCH INTO 変数数、DBMS_SQL.DEFINE_COLUMN の列位置を順に確認しましょう。

動的SQLやアプリ経由では、実行直前のSQL全文と取得処理をセットで見るのが近道です。必要な列を明示し、SELECTリストを安定させると、ORA-01007を防ぎやすくなります。

参考

ORA-01007 – Oracle Database Error Help

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