【Oracle】CASE式完全ガイド|条件集計・ORDER BY・GROUP BY・PIVOT代替・PL/SQL CASE文・NULL処理まで実例で解説

【Oracle】CASE式完全ガイド|条件集計・ORDER BY・GROUP BY・PIVOT代替・PL/SQL CASE文・NULL処理まで実例で解説 Oracle

Oracle の CASE式は、SQL 標準に準拠した条件分岐の仕組みです。IF ~ THEN ~ ELSE のようなロジックを SQL や PL/SQL の中に組み込めます。

CASE式は SELECT・WHERE・ORDER BY・GROUP BY・集計関数の中など、式(Expression)を書ける場所であればほぼどこでも使えます。単純な値の置き換えから、条件集計・動的ソート・PIVOT代替まで幅広く活用できます。

この記事でわかること

  • 単純 CASE と検索 CASE の構文と使い分け
  • SELECT / WHERE / ORDER BY / HAVING での CASE 活用
  • SUM(CASE WHEN …)による条件集計(クロス集計)
  • PIVOT を使わない行→列変換パターン
  • CASE と NULL の関係・ELSE 省略時の注意
  • PL/SQL CASE 文(IF-ELSIF との違い)
  • Oracle 23ai: GROUP BY カラムエイリアス(CASE 式の簡略化)
スポンサーリンク

CASE 式の 2 種類:単純 CASE と検索 CASE

種類 構文 得意なケース
単純 CASE CASE 列 WHEN 値1 THEN 結果1 ... END 1列の値で分岐するとき(等値比較のみ)
検索 CASE CASE WHEN 条件1 THEN 結果1 ... END 複数列・範囲・不等号・サブクエリなど複雑な条件
単純 CASE 式:値が一致したら結果を返す
-- 単純 CASE: CASE 列名 WHEN 値 THEN 結果 ... ELSE デフォルト END
SELECT employee_id,
       department_id,
       CASE department_id
           WHEN 10 THEN '総務部'
           WHEN 20 THEN '人事部'
           WHEN 30 THEN '購買部'
           WHEN 50 THEN '出荷部'
           ELSE        'その他'     -- いずれにも該当しない場合
       END AS dept_name
FROM employees
ORDER BY department_id;
検索 CASE 式:条件式で分岐する
-- 検索 CASE: CASE WHEN 条件 THEN 結果 ... ELSE デフォルト END
-- 範囲・不等号・複合条件・サブクエリなど任意の条件が使える
SELECT employee_id, last_name, salary,
       CASE
           WHEN salary >= 800000 THEN 'S(上位)'
           WHEN salary >= 500000 THEN 'A(中上位)'
           WHEN salary >= 300000 THEN 'B(中位)'
           ELSE                       'C(下位)'
       END AS salary_grade,
       -- 複数列を組み合わせた条件も可能
       CASE
           WHEN department_id = 50 AND salary < 300000 THEN '要確認'
           WHEN manager_id IS NULL                      THEN 'トップ'
           ELSE                                              '一般'
       END AS emp_flag
FROM employees;
ELSE を省略すると NULL になる
ELSE 句を省略した場合、どの WHEN にもマッチしない行は NULL が返ります。意図しない NULL が混入しないように、明示的に ELSE '不明'ELSE 0 を書くことを推奨します。

WHERE 句・HAVING 句での CASE 式

CASE 式は WHERE 句でも使えます。フラグ列の値によって絞り込み条件を切り替えるときに便利です。

WHERE 句での CASE 式
-- 区分コードによって条件を切り替える
SELECT employee_id, last_name, salary
FROM employees
WHERE salary >
      CASE department_id
          WHEN 10 THEN 400000   -- 総務部は 40 万以上
          WHEN 50 THEN 250000   -- 出荷部は 25 万以上
          ELSE        300000    -- その他は 30 万以上
      END;

-- HAVING 句でも同様に使える
SELECT department_id,
       AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) >
       CASE department_id
           WHEN 50 THEN 200000
           ELSE        350000
       END;

ORDER BY での CASE 式:動的な並び替え制御

CASE 式を ORDER BY に使うと、条件に応じてソート順を動的に変えることができます。特定の値を先頭や末尾に固定するパターンでよく使われます。

ORDER BY での CASE 式:特定値を先頭に固定
-- 特定のステータスを先頭に表示する(通常の ORDER BY では制御できない)
SELECT order_id, status, created_at
FROM orders
ORDER BY
    -- status = 'URGENT' を最優先(0)、その他は 1 で並列に扱い日付降順
    CASE WHEN status = 'URGENT'  THEN 0
         WHEN status = 'PENDING' THEN 1
         WHEN status = 'DONE'    THEN 2
         ELSE                         3
    END,
    created_at DESC;

-- NULL を末尾に固定する(NULLS LAST の代替)
SELECT employee_id, manager_id
FROM employees
ORDER BY
    CASE WHEN manager_id IS NULL THEN 1 ELSE 0 END,   -- NULL を後回し
    manager_id ASC;

SUM(CASE WHEN …):条件集計(クロス集計の定番パターン)

条件集計(Conditional Aggregation)は、GROUP BY と CASE を組み合わせて「縦持ちデータを横に集計する」手法です。SUM(CASE WHEN 条件 THEN 1 ELSE 0 END) で条件を満たす件数を集計できます。

条件集計の基本:部門別の給与区分人数を横に並べる
-- 各部門の給与帯別人数をクロス集計で一覧表示
SELECT
    department_id,
    COUNT(*)                                               AS total,
    SUM(CASE WHEN salary >= 800000 THEN 1 ELSE 0 END)     AS grade_s,
    SUM(CASE WHEN salary >= 500000
              AND salary <  800000 THEN 1 ELSE 0 END)     AS grade_a,
    SUM(CASE WHEN salary >= 300000
              AND salary <  500000 THEN 1 ELSE 0 END)     AS grade_b,
    SUM(CASE WHEN salary <  300000 THEN 1 ELSE 0 END)     AS grade_c
FROM employees
GROUP BY department_id
ORDER BY department_id;
-- 結果:
-- DEPT_ID  TOTAL  GRADE_S  GRADE_A  GRADE_B  GRADE_C
-- 10       3      1        1        1        0
-- 50       45     2        10       25       8
条件集計の応用:月別・カテゴリ別の売上を横に集計
-- 月ごとの売上を一行に横並びで集計(PIVOT の代替として広く使われる)
SELECT
    product_id,
    SUM(CASE WHEN TO_CHAR(order_date,'MM') = '01' THEN amount ELSE 0 END) AS jan,
    SUM(CASE WHEN TO_CHAR(order_date,'MM') = '02' THEN amount ELSE 0 END) AS feb,
    SUM(CASE WHEN TO_CHAR(order_date,'MM') = '03' THEN amount ELSE 0 END) AS mar,
    SUM(CASE WHEN TO_CHAR(order_date,'MM') = '04' THEN amount ELSE 0 END) AS apr,
    -- ...
    SUM(amount) AS total_year
FROM order_details
WHERE TO_CHAR(order_date,'YYYY') = '2025'
GROUP BY product_id
ORDER BY product_id;
COUNT(CASE WHEN …):条件を満たす件数だけカウント
-- SUM(CASE WHEN...) の代わりに COUNT + CASE で NULL を活用する書き方
-- COUNT は NULL をスキップするため、条件を満たさない行は NULL を返して除外できる

SELECT
    department_id,
    COUNT(*)                                                    AS total,
    COUNT(CASE WHEN salary >= 800000 THEN 1 END)               AS grade_s,
    COUNT(CASE WHEN salary >= 500000 AND salary < 800000 THEN 1 END) AS grade_a,
    COUNT(CASE WHEN salary <  300000 THEN 1 END)               AS grade_c,
    -- 平均も条件付きで計算できる
    ROUND(AVG(CASE WHEN department_id = 50 THEN salary END))   AS dept50_avg
FROM employees
GROUP BY department_id;

-- SUM(CASE WHEN .. THEN 1 ELSE 0 END) vs COUNT(CASE WHEN .. THEN 1 END)
-- どちらも同じ結果。COUNT 版の方が ELSE が不要でシンプル

CASE を使った行→列変換(PIVOT が使えない場合の代替)

Oracle PIVOT演算子(Oracle 11g+)で同じことができますが、IN () の値を動的に変えたい場合や古い環境では CASE による条件集計が代替になります。

CASE による PIVOT 代替パターン
-- 縦持ちデータ: (region, quarter, sales)
-- → 横持ちに変換: region ごとに Q1〜Q4 の列を作る

SELECT
    region,
    SUM(CASE WHEN quarter = 'Q1' THEN sales ELSE 0 END) AS q1_sales,
    SUM(CASE WHEN quarter = 'Q2' THEN sales ELSE 0 END) AS q2_sales,
    SUM(CASE WHEN quarter = 'Q3' THEN sales ELSE 0 END) AS q3_sales,
    SUM(CASE WHEN quarter = 'Q4' THEN sales ELSE 0 END) AS q4_sales,
    SUM(sales)                                           AS annual_total
FROM regional_sales
GROUP BY region
ORDER BY region;

-- Oracle 11g+ の PIVOT 構文(同じ結果):
-- SELECT * FROM regional_sales
-- PIVOT (SUM(sales) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'));

GROUP BY での CASE 式と Oracle 23ai の改善

Oracle 23ai より前は、GROUP BY に CASE 式を使う場合同じ式を SELECT と GROUP BY の両方に書く必要がありました。Oracle 23ai では SELECT のエイリアスを GROUP BY で使えるため、これが不要になりました。

GROUP BY で CASE 式を使う(23ai 前後の比較)
-- Oracle 23ai 以前: GROUP BY に CASE 式をそのまま繰り返す必要がある
SELECT
    CASE
        WHEN salary >= 800000 THEN 'S'
        WHEN salary >= 500000 THEN 'A'
        WHEN salary >= 300000 THEN 'B'
        ELSE                       'C'
    END AS salary_grade,
    COUNT(*) AS headcount,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY
    CASE
        WHEN salary >= 800000 THEN 'S'   -- ← 同じ CASE 式を繰り返す
        WHEN salary >= 500000 THEN 'A'
        WHEN salary >= 300000 THEN 'B'
        ELSE                       'C'
    END
ORDER BY salary_grade;

-- Oracle 23ai: エイリアス salary_grade を GROUP BY でそのまま使える
SELECT
    CASE
        WHEN salary >= 800000 THEN 'S'
        WHEN salary >= 500000 THEN 'A'
        WHEN salary >= 300000 THEN 'B'
        ELSE                       'C'
    END AS salary_grade,
    COUNT(*) AS headcount,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY salary_grade     -- ← エイリアスをそのまま使える(シンプル)
ORDER BY salary_grade;

CASE と NULL の注意点

CASE 式は WHEN NULL を等値比較で処理できません。NULL の判定には WHEN value IS NULL(検索 CASE)を使います。

NG: 単純 CASE で NULL を判定しようとする
-- 単純 CASE で NULL を WHEN に使っても NULL == NULL は UNKNOWN になり絶対マッチしない
SELECT employee_id,
       CASE manager_id
           WHEN NULL THEN 'トップ'     -- ← これは絶対に実行されない
           ELSE           '一般'
       END AS emp_type
FROM employees;
-- → manager_id が NULL でも 'トップ' にならず '一般' になる
OK: 検索 CASE で IS NULL を使う
-- 検索 CASE では IS NULL / IS NOT NULL が使える
SELECT employee_id,
       CASE
           WHEN manager_id IS NULL THEN 'トップ'      -- NULL を正しく判定
           ELSE                         '一般'
       END AS emp_type
FROM employees;

-- NULL を含む複合条件
SELECT employee_id, salary, commission_pct,
       CASE
           WHEN commission_pct IS NULL THEN salary              -- コミッションなし
           WHEN commission_pct = 0     THEN salary              -- コミッション 0%
           ELSE salary + salary * commission_pct                -- コミッションあり
       END AS total_income
FROM employees;

PL/SQL の CASE 文(SQL の CASE 式との違い)

PL/SQL では CASE 式(値を返す)と CASE 文(処理を分岐する)の2種類があります。CASE 文は IF-ELSIF-ELSE-END IF の代替として使えます。

PL/SQL CASE 文の構文(処理の分岐)
DECLARE
    v_grade CHAR(1) := 'A';
    v_msg   VARCHAR2(100);
BEGIN
    -- PL/SQL CASE 文(単純)
    CASE v_grade
        WHEN 'S' THEN v_msg := '特別評価です';
        WHEN 'A' THEN v_msg := '優秀です';
        WHEN 'B' THEN v_msg := '良好です';
        WHEN 'C' THEN v_msg := '要改善です';
        ELSE          v_msg := '評価区分外です';
    END CASE;
    DBMS_OUTPUT.PUT_LINE(v_msg);

    -- PL/SQL CASE 文(検索型): 複雑な条件で分岐
    DECLARE
        v_salary NUMBER := 650000;
    BEGIN
        CASE
            WHEN v_salary >= 800000 THEN DBMS_OUTPUT.PUT_LINE('グレードS');
            WHEN v_salary >= 500000 THEN DBMS_OUTPUT.PUT_LINE('グレードA');
            WHEN v_salary >= 300000 THEN DBMS_OUTPUT.PUT_LINE('グレードB');
            ELSE                         DBMS_OUTPUT.PUT_LINE('グレードC');
        END CASE;
    END;
END;
/
PL/SQL CASE 式(値を返す)の活用
DECLARE
    v_dept_id NUMBER := 50;
    v_msg     VARCHAR2(100);
BEGIN
    -- CASE 式: := の右辺に使って値を代入
    v_msg := CASE v_dept_id
                 WHEN 10 THEN '総務部への配属です'
                 WHEN 50 THEN '出荷部への配属です'
                 ELSE        'その他部門への配属です'
             END;
    DBMS_OUTPUT.PUT_LINE(v_msg);

    -- DBMS_OUTPUT.PUT_LINE の引数に直接 CASE 式を埋め込む
    DBMS_OUTPUT.PUT_LINE(
        '部門: ' ||
        CASE v_dept_id
            WHEN 10 THEN '総務部'
            WHEN 50 THEN '出荷部'
            ELSE        '不明'
        END
    );
END;
/
観点 CASE 文(Statement) IF-ELSIF(Statement)
値の返却 不可(処理の分岐のみ) 不可
可読性 値の等値比較なら CASE の方が簡潔 複雑な条件では読みやすい
WHEN の数が多い場合 CASE の方が縦に整理しやすい ELSIF が多くなり冗長
NULL 判定 検索 CASE で WHEN col IS NULL IF col IS NULL THEN

CASE 式とパフォーマンスの注意点

インデックスが使われない CASE のパターン
-- WHERE 句で列を CASE 式で変換すると、その列のインデックスが使われない
SELECT * FROM employees
WHERE CASE WHEN salary > 500000 THEN 'high' ELSE 'low' END = 'high';
-- → salary 列のインデックスが使用不可

-- OK: 条件をそのまま書く
SELECT * FROM employees WHERE salary > 500000;

-- 関数ベースインデックスを作れば CASE 式でもインデックス利用可能
CREATE INDEX idx_salary_grade
ON employees (
    CASE WHEN salary >= 800000 THEN 'S'
         WHEN salary >= 500000 THEN 'A'
         WHEN salary >= 300000 THEN 'B'
         ELSE 'C'
    END
);
-- 作成後: WHERE CASE WHEN salary >= 800000... END = 'S' でインデックスが使われる

まとめ

Oracle の CASE 式は SQL のあらゆる場所で使える強力な条件分岐の仕組みです。

  • 単純 CASE:1列の等値比較に適す。コード変換・マスター展開に活用
  • 検索 CASE:複雑な条件・範囲・NULL 判定(IS NULL)に使う
  • 条件集計SUM(CASE WHEN...)):クロス集計・月別集計の定番パターン
  • ORDER BY + CASE:特定値の先頭固定・動的ソートに強力
  • Oracle 23ai:GROUP BY でエイリアスが使えるようになり、CASE 式の繰り返しが不要に

CASE と DECODE の比較についてはCASE文とDECODE関数の違いと使い分け、PIVOT 演算子についてはPIVOTで縦持ちデータを横持ちに変換する方法も参照してください。