WITH FUNCTION句は、Oracle Database 12c以降で使える、SQL文の中に一時的なPL/SQL関数を定義する書き方です。そのSQLだけで使う小さな変換処理、判定処理、整形処理を、スキーマレベルのファンクションとして作成せずに閉じ込められます。
便利な一方で、通常の WITH 句、スキーマレベルのファンクション、CASE 式とは使いどころが違います。また、INSERT / UPDATE / DELETE / MERGE の中で使う場合は WITH_PLSQL の指定が必要です。この記事では、基本構文からDMLでの使い方、性能面の注意、避けたいパターンまで実務向けに整理します。通常のWITH句は OracleのWITH句完全ガイド、ファンクション全体の設計は PL/SQLプロシージャ・ファンクション完全ガイド も参考にしてください。
WITH FUNCTIONの基本構文- 複数関数を定義する書き方
- サブクエリやDMLで使う場合の
WITH_PLSQL - CASE式・通常ファンクションとの使い分け
- 大量データでの性能注意
- 実務で避けたいアンチパターン
WITH FUNCTION句とは
WITH FUNCTION は、SELECT文の WITH 句にPL/SQL関数を宣言する機能です。OracleのSQLリファレンスでは plsql_declarations として扱われ、定義した関数はそのSQL文と、その中のサブクエリから参照できます。関数オブジェクトをデータベースに残さないため、一時的な抽出SQLや検証SQLで特に便利です。
基本構文
基本形は、WITH の直後に FUNCTION を定義し、その後のSELECT本体で呼び出します。以下は、税抜価格から税込価格を計算するローカル関数の例です。
WITH
FUNCTION tax_price(p_price NUMBER) RETURN NUMBER IS
BEGIN
RETURN ROUND(p_price * 1.10);
END;
SELECT
product_id,
product_name,
price,
tax_price(price) AS price_incl_tax
FROM products
WHERE category_id = 10;
この関数はSQL文の中だけで有効です。CREATE FUNCTION で作成したわけではないため、実行後にデータベースオブジェクトとして残りません。
複数の関数を定義する
1つの WITH 句の中に複数の関数を定義できます。ただし、関数が増えすぎるとSQL全体の見通しが悪くなるため、2〜3個を超えるなら通常のパッケージ化も検討します。
WITH
FUNCTION normalize_code(p_code VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN UPPER(TRIM(p_code));
END;
FUNCTION price_rank(p_price NUMBER) RETURN VARCHAR2 IS
BEGIN
IF p_price >= 100000 THEN
RETURN 'HIGH';
ELSIF p_price >= 10000 THEN
RETURN 'MIDDLE';
ELSE
RETURN 'LOW';
END IF;
END;
SELECT
product_id,
normalize_code(product_code) AS normalized_code,
price_rank(price) AS rank_name
FROM products;
CASE式で足りるなら関数化しない
単純な条件分岐であれば、WITH FUNCTION より CASE 式のほうが読みやすく、SQLエンジンにも素直です。たとえば価格帯を返すだけなら、次のように CASE で十分です。CASE式の詳しい使い方は Oracle CASE式完全ガイド にまとめています。
SELECT
product_id,
CASE
WHEN price >= 100000 THEN 'HIGH'
WHEN price >= 10000 THEN 'MIDDLE'
ELSE 'LOW'
END AS rank_name
FROM products;
WITH FUNCTION が向いているのは、同じ判定を複数箇所で使う、文字列整形が長い、例外処理を含めたい、SQLだけでは読みづらいロジックを一時的に閉じ込めたい、といった場面です。
WITH句のサブクエリと組み合わせる
WITH FUNCTION は、通常の共通表式と同じ WITH 句の中で使えます。関数で値を整形し、その結果を共通表式で集計するような書き方もできます。
WITH
FUNCTION normalize_dept(p_name VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN UPPER(REPLACE(TRIM(p_name), ' ', '_'));
END;
dept_sales AS (
SELECT
normalize_dept(department_name) AS dept_key,
amount
FROM sales_source
WHERE sales_month = :target_month
)
SELECT
dept_key,
SUM(amount) AS total_amount
FROM dept_sales
GROUP BY dept_key
ORDER BY dept_key;
共通表式で段階的にSQLを整理したい場合は、通常のWITH句と役割を分けると読みやすくなります。再帰WITHや階層問い合わせに近い考え方は 再帰WITH句と階層問い合わせの実用パターン も参考になります。
DMLで使う場合はWITH_PLSQLを指定する
WITH FUNCTION をトップレベルのSELECTで使う場合は、そのまま書けます。一方、INSERT / UPDATE / DELETE / MERGE の中で使う場合は、トップレベル文に WITH_PLSQL を指定します。これは最適化ヒントではなく、PL/SQL宣言付きWITH句をその文で使うための指定です。
INSERT /*+ WITH_PLSQL */ INTO product_export (
product_id,
normalized_code,
price_incl_tax
)
WITH
FUNCTION normalize_code(p_code VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN UPPER(TRIM(p_code));
END;
FUNCTION tax_price(p_price NUMBER) RETURN NUMBER IS
BEGIN
RETURN ROUND(p_price * 1.10);
END;
SELECT
product_id,
normalize_code(product_code),
tax_price(price)
FROM products
WHERE export_flag = 'Y';
動的にSQLを組み立てる場合は、構文がさらに読みづらくなります。動的SQLの基本やバインド変数の扱いは Oracle EXECUTE IMMEDIATE完全ガイド も確認してください。
UPDATEで使う例
更新対象行ごとに整形関数を呼びたい場合も、トップレベルの UPDATE に WITH_PLSQL を付けます。ただし、大量更新では関数呼び出しがボトルネックになりやすいため、単純な変換ならSQL関数だけで書けないか先に検討します。
UPDATE /*+ WITH_PLSQL */ products p
SET p.normalized_code = (
WITH
FUNCTION normalize_code(p_code VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN UPPER(TRIM(REPLACE(p_code, '-', '')));
END;
SELECT normalize_code(p.product_code)
FROM dual
)
WHERE p.normalized_code IS NULL;
通常ファンクションとの違い
WITH FUNCTION は、そのSQL専用のローカル関数です。再利用したい処理、権限設計が必要な処理、テスト対象にしたい処理は、通常のファンクションやパッケージに分けたほうが向いています。
DETERMINISTIC や RESULT_CACHE を含むファンクション設計、AUTHID の考え方は、プロシージャ・ファンクション完全ガイド 側で詳しく扱っています。
性能面の注意点
WITH FUNCTION は便利ですが、行ごとにPL/SQL関数が呼ばれる形になるため、対象行が多いほど処理時間に影響します。100行程度の抽出では気にならなくても、数十万行・数百万行のバッチでは大きな差になることがあります。
避けたいアンチパターン
WITH FUNCTIONは「SQLに何でも書ける道具」ではありません。特に次のような使い方は、実務では避けたほうが安全です。
エラーになりやすいポイント
WITH FUNCTIONは構文上の位置が少し特殊なため、エラー時はまず「トップレベル文」「WITH_PLSQL」「関数の終了セミコロン」を確認します。
-- 確認ポイント -- 1. FUNCTION定義はWITH句の先頭側にあるか -- 2. 各FUNCTIONのENDの後にセミコロンがあるか -- 3. DMLで使う場合、トップレベル文に /*+ WITH_PLSQL */ があるか -- 4. 同名の列名・関数名が衝突していないか -- 5. 関数内でSQLから呼べない処理をしていないか
特にDML内で WITH_PLSQL を忘れると、構文自体はそれらしく見えても失敗します。この指定は実行計画を変えるためのヒントではなく、PL/SQL宣言付きWITH句を許可するためのものだと覚えておくと混乱しにくいです。
実務での使いどころ
WITH FUNCTIONは、恒久的な部品というより、SQLを自己完結させるための補助道具です。次のような場面では特に使いやすいです。
よくある質問
/*+ WITH_PLSQL */ を指定します。これは最適化ヒントではなく構文を許可する指定です。まとめ
WITH FUNCTION句を使うと、SQL文の中にPL/SQL関数を定義し、そのSQLだけで使えるローカル関数として利用できます。一時的な抽出SQL、調査SQL、ETL前処理のように、オブジェクトを増やさずに小さなロジックを同梱したい場面で便利です。
一方で、DMLでは WITH_PLSQL が必要であり、大量行に対する関数呼び出しは性能面の注意が必要です。単純な条件分岐はCASE式、再利用する処理は通常ファンクション、SQLを自己完結させたい一時処理はWITH FUNCTION、という使い分けを意識しましょう。

