【SQL】WITH句を使って副問合せを再利用する方法

SQLの「WITH句」について、わかりやすく解説します。大量データを扱ったり、複雑な副問合せが入り組んだクエリを書いたりする際に知っておくと、とても役に立ちます。

WITH句とは?

WITH句とは、一時的な名前付きの副問合せを定義する仕組みです。通常の副問合せと比べて、下記のようなメリットがあります。

再利用が簡単
同じ副問合せを何度も繰り返し書かなくていいため、クエリ全体の重複を大幅に減らすことができます。

可読性が向上
複数の副問合せをそれぞれWITH句に切り出すことで役割が明確になり、長いクエリでも構造を把握しやすくなります。

再帰的な問い合わせが可能
WITH RECURSIVE を使うと、階層構造やツリー構造を再帰的に取得できるようになります。従来はテーブルを自己結合する手間が多かったケースも、スッキリと記述できます。

基本的なWITH句の構文

WITH サブクエリ名 AS (
  SELECT
    カラム1,
    カラム2
  FROM
    テーブルA
  WHERE
    カラム1 > 100
)
SELECT
  サブクエリ名.カラム1,
  サブクエリ名.カラム2
FROM
  サブクエリ名;

WITH サブクエリ名 AS (…) で定義した副問合せを、あたかもテーブルのようにメインのSELECTで利用できます。

サンプルケース

WITH adults AS (
  SELECT
    user_id,
    name,
    age
  FROM
    users
  WHERE
    age >= 20
)
SELECT
  user_id,
  name,
  age
FROM
  adults;

上記では、adults という副問合せ(テーブルのようなもの)で「20歳以上のユーザー情報」をまとめています。
従来の副問合せをわざわざネストしなくてもよいので、可読性・保守性が上がります。

複数の名前付き副問合せを定義する

WITH句では、カンマ区切りでいくつでも定義できます。例えば、段階的にデータを絞り込んだり、異なる条件で抽出した結果を結合したりする場合に便利です。

WITH
  condition_a AS (
    SELECT ...
    FROM ...
    WHERE ...
  ),
  condition_b AS (
    SELECT ...
    FROM ...
    WHERE ...
  )
SELECT
  ...
FROM
  condition_a
JOIN
  condition_b
    ON condition_a.xxx = condition_b.xxx;

先に condition_a を定義し、次に condition_b を定義――というように、ステップごとにロジックを分割して書けます。

読み手は「最初にどんなデータを作っているか」「最後にどう利用しているか」を簡単に理解できるので、SQLのメンテナンスが楽になります。

再帰的な副問合せで階層構造を取得する

WITH RECURSIVE を使用すると、自己参照テーブルにおける親子関係やツリー構造を再帰的に辿って取得できます。これは従来の自己結合を繰り返す手法よりもシンプルで読みやすいです。

テーブル例

employee_id name manager_id
1 Alice NULL
2 Bob 1
3 Charlie 1
4 Diana 2
5 Eric 2
6 Fumiko 3

manager_id は、上司の employee_id を参照しています。Alice は最上位のため manager_id がNULL。

WITH RECURSIVEを使った例

WITH RECURSIVE employee_hierarchy AS (
  -- (1) 階層のスタート: manager_id が NULL の社員
  SELECT
    employee_id,
    name,
    manager_id,
    1 AS level
  FROM employees
  WHERE manager_id IS NULL

  UNION ALL

  -- (2) 再帰呼び出し: 直前で取得した社員を上司にもつ社員を取得
  SELECT
    e.employee_id,
    e.name,
    e.manager_id,
    eh.level + 1 AS level
  FROM employees e
  JOIN employee_hierarchy eh
    ON e.manager_id = eh.employee_id
)
SELECT
  employee_id,
  name,
  manager_id,
  level
FROM
  employee_hierarchy
ORDER BY
  level,
  employee_id;

初回のSELECTで「トップレベル(上司不在)の社員」を取り出し、UNION ALLの後半で「その社員を上司に持つ部下」を再帰的に取得しています。

eh.level + 1 AS level のように階層の深さをインクリメントすると、どの階層にいるのかが一目瞭然になります。

まとめ

副問合せを多用して複雑になっているSQLがあれば、ぜひこのWITH句を活用してみてください。クエリが見通し良くなり、今までよりもスムーズに管理できるはずです。