【Oracle】ORA-06504の原因と解決方法|Return types of Result Set variables or query do not match の直し方

【Oracle】ORA-06504の原因と解決方法|Return types of Result Set variables or query do not match の直し方 Oracle

ORA-06504: PL/SQL: Return types of Result Set variables or query do not match は、REF CURSORFETCH INTO で、SELECT結果の列構成と受け取り側の変数・レコード型が合わない時に発生するエラーです。列数が違う、列の順番が違う、型が違う、SELECT * の結果が変わった、というケースでよく起きます。

似たカーソル系エラーとして ORA-01001ORA-01002 がありますが、ORA-06504はカーソルの状態ではなく、カーソルから返る行の形と受け取り側の形が合っていないことが中心です。

先に結論
ORA-06504が出たら、まず OPEN ... FOR SELECT のSELECT列と、FETCH ... INTO の変数・レコードの数、順番、型を見比べます。SELECT * は列追加や列順変更で壊れやすいため、明示的な列指定、%ROWTYPE、専用レコード型に寄せます。発生行は ORA-06512 の行番号から追います。
スポンサーリンク

ORA-06504とは

ORA-06504は、PL/SQLでResult Setを受け取る変数やレコード型と、実際の問い合わせ結果が一致しない時の実行時エラーです。特に SYS_REFCURSOR は戻り行の形をコンパイル時に強く固定しないため、実行時に初めて不一致が見つかることがあります。

不一致の種類 起きやすい場所
列数が違う SELECTは3列、FETCH先は2変数 FETCH INTO
列順が違う 数値列を文字列変数に入れる レコード型、明示変数
型が違う DATEをNUMBERに受ける 動的SQL、REF CURSOR
SELECT * が変わる 表に列追加、ビュー定義変更 保守後、リリース後
強い型付けカーソルとSELECTが違う RETURN型とOPEN FORの列が違う パッケージAPI

Oracle公式のORA-06504説明でも、Result Setの変数または問い合わせの戻り型が一致しないことが原因として示されています。つまり、対処では「返す側」と「受ける側」の行構造を揃えることが最優先です。

列数が合わない基本例

もっとも分かりやすいのは、カーソルが返す列数と FETCH INTO の受け取り変数の数が違うケースです。

ora06504-column-count-mismatch.sql
DECLARE
  l_cur SYS_REFCURSOR;
  l_empno employees.employee_id%TYPE;
  l_name  employees.first_name%TYPE;
BEGIN
  OPEN l_cur FOR
    SELECT employee_id, first_name, hire_date
    FROM employees;

  FETCH l_cur INTO l_empno, l_name;
  -- SELECTは3列、FETCH先は2変数なので一致しない

  CLOSE l_cur;
END;
/

この場合は、SELECT列を2列にするか、FETCH先を3つに増やします。どちらが正しいかは、呼び出し元が本当に必要としている列に合わせて決めます。

ora06504-column-count-fixed.sql
DECLARE
  l_cur SYS_REFCURSOR;
  l_empno employees.employee_id%TYPE;
  l_name  employees.first_name%TYPE;
  l_hired employees.hire_date%TYPE;
BEGIN
  OPEN l_cur FOR
    SELECT employee_id, first_name, hire_date
    FROM employees;

  FETCH l_cur INTO l_empno, l_name, l_hired;

  CLOSE l_cur;
END;
/

列の型・順番が合わない例

列数が合っていても、列の順番や型が違うとORA-06504になります。SELECT側の1列目は NUMBER なのに、FETCH先の1つ目が日付やレコードの別項目になっている、といったケースです。

ora06504-column-order-mismatch.sql
DECLARE
  TYPE t_emp_rec IS RECORD (
    emp_name employees.first_name%TYPE,
    emp_id   employees.employee_id%TYPE
  );

  l_cur SYS_REFCURSOR;
  l_rec t_emp_rec;
BEGIN
  OPEN l_cur FOR
    SELECT employee_id, first_name
    FROM employees;

  FETCH l_cur INTO l_rec;
  -- レコードは name, id の順だが、SELECTは id, name の順

  CLOSE l_cur;
END;
/

レコード型へFETCHする場合は、SELECT列の順番とレコード項目の順番を一致させます。名前が同じでも、FETCHでは列名ではなく位置で対応する点に注意します。

確認点 NG例 修正
列数 SELECTが3列、FETCH先が2項目 同じ数に揃える
列順 SELECTはid,name、レコードはname,id 順番を揃える
データ型 DATEをNUMBERへFETCH %TYPEや正しい型にする
NULL可否 NULLを想定しない後続処理 戻り値の意味を決める
暗黙変換 文字列日付をDATE扱い 明示変換または型を揃える

SELECT * が危険な理由

ORA-06504でよくあるのが、SELECT * を返すREF CURSORです。表やビューに列が追加されると、FETCH先のレコード型と列数・列順が変わり、以前は動いていたコードが急に落ちることがあります。

ora06504-select-star-risk.sql
OPEN l_cur FOR
  SELECT *
  FROM employees;

-- employees に列が追加されると、呼び出し側のFETCH先とズレることがある

外部へ返すカーソルやパッケージAPIでは、SELECT * を避けて列を明示します。APIとして返す列を固定できるため、呼び出し側との契約が壊れにくくなります。

ora06504-explicit-columns.sql
OPEN l_cur FOR
  SELECT employee_id,
         first_name,
         hire_date
  FROM employees;

-- 返す列を固定する

%ROWTYPEで揃える

表やカーソルの行構造をそのまま受けるなら、%ROWTYPE を使うと列構造を揃えやすくなります。ただし、%ROWTYPE でもSELECT列の構造と一致していることが前提です。

ora06504-rowtype-fixed.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id, first_name, hire_date
    FROM employees;

  l_rec c_emp%ROWTYPE;
BEGIN
  OPEN c_emp;
  FETCH c_emp INTO l_rec;
  CLOSE c_emp;
END;
/

%ROWTYPE%TYPE の使い方は PL/SQL変数・定数ガイド、基本的なカーソルの流れは 明示的カーソルガイド も参考になります。

SYS_REFCURSORと強い型付けREF CURSOR

SYS_REFCURSOR は柔軟ですが、どんな行構造が返るかを呼び出し側が把握していないと不一致が起きやすくなります。一方、強い型付けREF CURSORは戻り型を宣言できるため、APIの契約を明確にしやすいです。

種類 特徴 ORA-06504対策
SYS_REFCURSOR 戻り行の形が柔軟 列仕様をコメントやAPI仕様で固定する
弱い型付けREF CURSOR 任意のSELECTをOPENできる 呼び出し側と列構造を共有する
強い型付けREF CURSOR RETURN型を宣言する SELECT列をRETURN型に合わせる
通常カーソル カーソル定義と%ROWTYPEを使いやすい PL/SQL内部処理に向く
strong-ref-cursor-return-type.sql
DECLARE
  TYPE t_emp_cur IS REF CURSOR RETURN employees%ROWTYPE;
  l_cur t_emp_cur;
  l_rec employees%ROWTYPE;
BEGIN
  OPEN l_cur FOR
    SELECT *
    FROM employees;

  FETCH l_cur INTO l_rec;
  CLOSE l_cur;
END;
/

REF CURSORの設計全体は REF CURSORガイド に詳しくまとめています。外部アプリに返す場合は、返す列の契約を固定することが特に重要です。

JDBCやODP.NETから呼ぶ場合

Javaの CallableStatement、.NETの OracleDataReader、PythonやPHPのカーソル取得でREF CURSORを受ける場合も、DB側が返す列仕様とアプリ側が読む列仕様がズレるとORA-06504の原因になります。アプリ側では「何列目に何型が来るか」を前提にしているため、DB側のSELECT列を変更した時は呼び出し側も同時に確認します。

確認点 よくあるズレ 対処
OUTパラメータの種類 REF CURSORのつもりが別型で受けている 登録するOUT型を確認する
列の読み取り順 1列目を文字列として読むが実際はNUMBER 列順と型をAPI仕様に書く
列名参照 別名を変えてアプリ側が読めない 列別名を固定する
NULLの扱い NULLを想定しない型変換で落ちる NULL可否を仕様に含める
DB側のSELECT変更 列追加や順番変更をアプリに伝えていない APIバージョンを分ける
ref-cursor-api-contract.sql
-- APIとして返す列仕様を固定する
OPEN p_result FOR
  SELECT employee_id AS employee_id,
         first_name  AS first_name,
         hire_date   AS hire_date
  FROM employees
  WHERE department_id = p_department_id;

-- 呼び出し側は 1: employee_id(NUMBER), 2: first_name(VARCHAR2), 3: hire_date(DATE) として読む

外部アプリ向けのREF CURSORでは、列を増やしたい時に既存のカーソル定義を変えるより、新しいプロシージャや新しい戻り列セットとして分ける方が安全なことがあります。特に複数アプリから呼ばれているパッケージAPIでは、列数・列順・列別名を互換性の一部として扱います。

動的SQLで起きる場合

動的SQLでSELECT句を条件によって変えると、呼び出し側のFETCH先と合わないパターンが生まれやすくなります。検索条件だけを動的にし、返す列は固定する設計にするとORA-06504を防ぎやすくなります。

dynamic-sql-fixed-select-list.sql
OPEN l_cur FOR
  'SELECT employee_id, first_name, hire_date
     FROM employees
    WHERE department_id = :dept_id'
USING p_department_id;

-- WHERE句は動的でも、SELECT列は固定する

動的SQLでは、条件によって employee_id, first_name だけ返す経路と、employee_id, first_name, hire_date を返す経路が混ざると危険です。呼び出し側が1つのFETCH先を使うなら、返す列数も1つに固定します。

BULK COLLECTで起きる場合

BULK COLLECT でも、SELECT結果とコレクション要素の型が合っていないとエラーになります。複数列を一括取得するなら、レコード型のコレクションを使うか、列ごとのコレクションを用意します。

bulk-collect-record-type.sql
DECLARE
  TYPE t_emp_rec IS RECORD (
    employee_id employees.employee_id%TYPE,
    first_name  employees.first_name%TYPE,
    hire_date   employees.hire_date%TYPE
  );
  TYPE t_emp_tab IS TABLE OF t_emp_rec;
  l_emps t_emp_tab;
BEGIN
  SELECT employee_id, first_name, hire_date
  BULK COLLECT INTO l_emps
  FROM employees;
END;
/

BULK COLLECTの基本は BULK COLLECT / FORALLガイド、コレクション型の整理は PL/SQLコレクション型ガイド が参考になります。

ORA-06512から発生行を追う

ORA-06504は、実際には FETCH 行やREF CURSORを返す関数の呼び出し行で見つかることが多いです。エラースタックに ORA-06512 が出ている場合は、行番号から該当するFETCHやOPEN FORを確認します。ソース確認には USER_SOURCE を使います。

ora06504-error-stack.txt
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at "APP.PKG_EMP", line 42
ORA-06512: at line 1
check-user-source-ora06504.sql
SELECT line,
       text
FROM user_source
WHERE name = 'PKG_EMP'
  AND type = 'PACKAGE BODY'
  AND line BETWEEN 35 AND 50
ORDER BY line;

行番号の読み方は ORA-06512の読み方 を使うと整理しやすいです。カーソルが閉じている、FETCH順序がおかしい、といった状態の問題なら ORA-01001ORA-01002 側も確認します。

症状別の直し方

症状 原因候補 直し方
FETCH行で落ちる SELECT列とFETCH先が不一致 列数・順番・型を揃える
リリース後に急に落ちる SELECT * の対象表やビューが変わった 明示列にする
特定の条件だけ落ちる 動的SQLのSELECT句が条件で変わる 返す列構造を固定する
外部アプリから呼ぶと落ちる REF CURSORの列仕様が共有されていない API仕様として列名・型・順番を固定する
BULK COLLECTで落ちる コレクション要素型とSELECT列が不一致 レコード型または列ごとのコレクションにする

チェックリスト

項目 OKの状態
SELECT列数とFETCH先の数が同じ 1対1で対応している
SELECT列順とレコード項目順が同じ 位置で対応しても問題ない
型が一致している %TYPEや%ROWTYPEでズレを減らしている
SELECT * を使っていない 外部へ返す列は明示している
動的SQLでも返す列構造が固定 条件で列数が変わらない
ORA-06512の行番号を確認した FETCH行またはOPEN FOR行が特定できている

よくある質問

列名が同じなら順番が違っても大丈夫ですか?

FETCHでは基本的に位置で対応します。列名が同じでも、SELECT列の順番と受け取り側の順番が違うと不一致になります。

SYS_REFCURSORを使うと型チェックされないのですか?

柔軟に使える一方で、戻り行の形をコンパイル時に固定しにくいです。そのため、実行時にORA-06504として見つかることがあります。

SELECT * を使ってはいけませんか?

内部の一時的な処理なら使える場面もありますが、REF CURSORで外部へ返すAPIや長期保守するコードでは避ける方が安全です。

ORA-01001やORA-01002との違いは何ですか?

ORA-01001やORA-01002はカーソルの状態やFETCH順序の問題です。ORA-06504は返る行の形と受け取り側の形が合わない問題です。

まとめ

ORA-06504は、REF CURSORやFETCH INTOで、問い合わせ結果と受け取り側の列数・順番・型が一致しない時に発生します。SYS_REFCURSOR、動的SQL、SELECT *、レコード型へのFETCHで特に注意が必要です。

対処では、SELECT列とFETCH先を1列ずつ照合し、必要なら明示列、%TYPE%ROWTYPE、専用レコード型に揃えます。発生行は ORA-06512 で追い、外部へ返すREF CURSORは列仕様をAPI契約として固定しておくと再発を防ぎやすくなります。

参考

ORA-06504 – Oracle Database Error Help

Cursors Overview – Oracle Database PL/SQL Language Reference

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