IPA|情報処理技術者試験

LATERAL JOINとAPPLY句を完全制覇!DBスペシャリスト午後試験の「トップN問題」SQL攻略法






LATERAL JOINとAPPLY句の解説│データベーススペシャリスト試験対策


データベーススペシャリスト試験や応用情報技術者試験の学習で登場する「LATERAL JOIN」や「APPLY 句」。参考書を読んでも「各行に関数のようにサブクエリを適用する」という説明だけでは、具体的なイメージが湧きにくいと感じていませんか?「通常の JOIN や相関サブクエリと何が違うの?」「どんな場面で使うと便利なの?」といった疑問は、多くの学習者が抱える共通の悩みです。

これらの構文は、SQL の表現力を飛躍的に向上させる強力な武器ですが、その概念を掴むには少しコツがいります。本質は「FROM 句で指定したテーブルの行を 1 行ずつ取り出し、その行の値をパラメータとしてサブクエリを実行する」というループ処理のようなイメージです。

この記事では、LATERAL JOIN と APPLY 句が持つこの「ループ処理」のイメージを、豊富な図解と具体的な SQL を使って徹底的に可視化します。読み終える頃には、複雑なデータ抽出要件にもエレガントに対応できる SQL の新たな扉が開かれているはずです。午後試験の難解な問い合わせはもちろん、実務でのデータ分析やレポーティング業務の効率化にも直結するこの強力な機能を、ぜひマスターしましょう。


LATERAL JOIN / APPLY句が必要な理由│相関サブクエリの限界を超える午後試験対策

SQLを学習すると、INNER JOINLEFT JOIN、そしてGROUP BYといった強力な道具を手にします。しかし、実務やデータベーススペシャリスト試験の午後問題では、これらだけではエレガントに解決できない複雑な要件に遭遇します。それが「グループごとの上位 N 件を取得したい」という典型的なシナリオです。

例えば、「各部署に所属する社員のうち、給与が高いトップ 3 名の情報をすべて取得したい」と考えてみましょう。

通常のJOINとGROUP BYの限界

GROUP BYを使えば、「各部署の最高給与額」を求めることは簡単です。

SELECT
  department_id,
  MAX(salary) AS max_salary
FROM
  employees
GROUP BY
  department_id;

しかし、この結果は「誰が」その給与を受け取っているのか分かりません。employee_idemployee_nameを追加しようとすると、GROUP BY句にも追加する必要があり、意図した結果とは異なってしまいます。JOINを駆使して自己結合を繰り返したり、ウィンドウ関数 (ROW_NUMBER()) を使ったりする方法もありますが、SQLが複雑になりがちです。

相関サブクエリの限界

「じゃあ、行ごとにサブクエリを実行する相関サブクエリならどうだろう?」と考えるかもしれません。確かに相関サブクエリは便利ですが、決定的な制約があります。

  1. 単一の値しか返せない(スカラ・サブクエリ): SELECT句に書く相関サブクエリは、結果が 1 行 1 列の値でなければエラーになります。「トップ 3 名」のような複数行の結果を返すことはできません。
  2. FROM句で使えない: 相関サブクエリはSELECT句やWHERE句で主問い合わせの列を参照しますが、FROM句の中で主問い合わせの別のテーブルの列を参照することはできません。

このように、既存の機能だけでは「主問い合わせの各行をパラメータとして、別のテーブルや関数を呼び出し、その複数行・複数列の結果セットを結合する」という処理を直感的に書くことが困難でした。

この課題を解決するために登場したのが、LATERAL JOIN(PostgreSQL, Oracleなど)とAPPLY句(SQL Server, Oracleなど)なのです。これらは、FROM句の中で相関サブクエリのように振る舞うことを可能にし、SQLの表現力を大きく広げました。まさに「各行に関数のようにサブクエリを適用する」ための機能と言えるでしょう。


LATERAL JOINの仕組みと構文│図解で学ぶ「行ごとループ処理」のSQL

LATERAL JOINの動きを理解する最良の方法は、プログラミング言語における「For-Each ループ」をイメージすることです。つまり、テーブル A の各行に対して、その行のデータを使ってテーブル B に対する問い合わせ(サブクエリ)を実行し、結果を結合するという処理を繰り返します。

言葉だけでは分かりにくいので、具体的なテーブルと SQL で処理フローを追ってみましょう。

サンプルテーブル

ここでは、シンプルな「部署」テーブルと「従業員」テーブルを使用します。

departments テーブル

department_id department_name
1 営業部
2 開発部
3 人事部

employees テーブル

employee_id employee_name department_id salary
101 田中 1 800
102 鈴木 1 750
103 佐藤 1 780
104 高橋 1 700
201 伊藤 2 900
202 渡辺 2 850
203 山本 2 920
301 中村 3 600

「各部署の給与トップ2社員」を取得するSQL

このデータから「各部署の給与トップ 2 名」を取得する SQL (PostgreSQL, Oracle 構文) は以下のようになります。

SELECT
  d.department_name,
  e_top.employee_name,
  e_top.salary
FROM
  departments AS d, -- Oracleの場合はカンマ区切りも可
  LATERAL (
    SELECT
      e.employee_name,
      e.salary
    FROM
      employees AS e
    WHERE
      e.department_id = d.department_id -- ★外側のd.department_idを参照
    ORDER BY
      e.salary DESC
    LIMIT 2 -- PostgreSQLの場合。Oracleでは FETCH FIRST 2 ROWS ONLY
  ) AS e_top;

※社員が一人もいない部署も表示したい場合は LEFT JOIN LATERAL (...) ON true; とします。

図解:LATERAL JOINの内部的な処理フロー

この SQL がどのように実行されるかを、図と共にステップ・バイ・ステップで見ていきましょう。

  1. 【ループ開始】 まず、主問い合わせの FROM departments AS d が評価され、departments テーブルの 1行目department_id=1, 営業部)が取り出されます。
  2. 【サブクエリ実行】 次に LATERAL 句の中のサブクエリが実行されます。このとき、1.で取り出された d.department_id の値(1が、サブクエリ内の WHERE e.department_id = d.department_idd.department_id に引き渡されます。
  3. 【結果取得】 サブクエリは実質的に「営業部 (department_id=1) の社員を給与の高い順に 2 名取得する」という処理になり、田中 (800)佐藤 (780) の 2 行が結果として返されます。
  4. 【結果の結合】 departments の 1 行目(営業部)と、サブクエリの結果 2 行が結合され、最終的な結果セットに 2 行が追加されます。
  5. 【次のループへ】 departments テーブルの 2行目department_id=2, 開発部)が取り出され、再び 2.~4. の処理が繰り返されます。開発部の場合は 山本 (920)伊藤 (900) が取得・結合されます。
  6. 【ループ終了】 departments テーブルのすべての行についてこの処理が完了するまで繰り返されます。

このように LATERAL というキーワードは、「(主問い合わせの)横で、行ごとにサブクエリを都度実行する」というイメージを持つと、その強力な機能が理解しやすくなります。



APPLY句の使い方 (SQL Server)│CROSS APPLYとOUTER APPLYを徹底解説

PostgreSQL や Oracle で LATERAL JOIN が導入される前から、SQL Server には同様の機能を実現する APPLY 句が存在しました。APPLY句は、LATERAL JOIN と同じく「行ごとに関数やサブクエリを適用する」という強力な機能を提供します。

APPLY には CROSS APPLYOUTER APPLY の 2 種類があり、それぞれ通常の JOIN における INNER JOINLEFT OUTER JOIN の関係に似ています。

CROSS APPLY:INNER JOINのように振る舞う

CROSS APPLY は、INNER JOINJOIN LATERAL と同じように動作します。つまり、左側(主)のテーブルの各行に対して右側のサブクエリを評価し、サブクエリが 1 行以上の結果を返した場合にのみ、その行を結果セットに含めます。

先の「各部署の給与トップ2社員」の例を SQL Server の構文で書くと以下のようになります。

SELECT
  d.department_name,
  e_top.employee_name,
  e_top.salary
FROM
  departments AS d
CROSS APPLY (
  SELECT
    e.employee_name,
    e.salary
  FROM
    employees AS e
  WHERE
    e.department_id = d.department_id
  ORDER BY
    e.salary DESC
  OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY -- SQL Server 2012以降
) AS e_top;

もし、所属する社員が一人もいない部署(例えば新設された「総務部」)があった場合、その部署はサブクエリの結果が 0 行となるため、CROSS APPLY では最終的な結果から除外されます。

OUTER APPLY:LEFT JOINのように振る舞う

一方、OUTER APPLYLEFT OUTER JOINLEFT JOIN LATERAL と同じように動作します。CROSS APPLY との決定的な違いは、サブクエリの結果が 0 行だったとしても、左側(主)のテーブルの行は結果セットに残る点です。その際、サブクエリから返されるはずだった列は NULL で補完されます。

先ほどのクエリを OUTER APPLY に変更してみましょう。

SELECT
  d.department_name,
  e_top.employee_name,
  e_top.salary
FROM
  departments AS d
OUTER APPLY ( -- ここを変更
  SELECT
    e.employee_name,
    e.salary
  FROM
    employees AS e
  WHERE
    e.department_id = d.department_id
  ORDER BY
    e.salary DESC
  OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY
) AS e_top;

このクエリであれば、社員が一人もいない「総務部」も結果に表示され、employee_namesalary の列には NULL が入ります。これは、「全〇〇の一覧を出しつつ、関連する上位 N 件の情報もあれば表示したい」といった要件で非常に役立ちます。

APPLY と LATERAL JOIN の関係まとめ

これらの関係性を表にまとめると、以下のようになります。

構文 動作のイメージ サブクエリから0件の場合の挙動
JOIN LATERAL INNER JOIN 相当 主テーブルの行も結果から除外される
LEFT JOIN LATERAL LEFT JOIN 相当 主テーブルの行は NULL 埋めで残る
CROSS APPLY INNER JOIN 相当 主テーブルの行も結果から除外される
OUTER APPLY LEFT JOIN 相当 主テーブルの行は NULL 埋めで残る

基本的には、標準SQL準拠の LATERAL JOIN と、SQL Server独自の APPLY はほぼ同じ機能を提供していると理解して問題ありません。どちらの構文に触れる機会が多いかは、普段利用しているデータベース製品によります。


実践的な活用シナリオとDBスペシャリスト午後試験対策

LATERAL JOINAPPLY は、単なるSQLのテクニックに留まらず、実際の業務システムや試験問題で頻出する「トップN問題」をエレガントに解決するための強力な思考ツールです。ここでは、より具体的なシナリオを見ていきましょう。

シナリオ1:ECサイト - カテゴリごとの最新レビューを3件取得

要件: Webサイトに商品カテゴリの一覧を表示し、各カテゴリについてユーザーから投稿された最新のレビューを3件ずつ表示したい。

この要件では、レビューがまだ投稿されていないカテゴリも表示する必要があるため、LEFT JOIN LATERALOUTER APPLY が最適です。

-- PostgreSQL / Oracle
SELECT
  c.category_name,
  r_latest.review_text,
  r_latest.created_at
FROM
  product_categories AS c
LEFT JOIN LATERAL (
  SELECT
    r.review_text,
    r.created_at
  FROM
    reviews AS r
  WHERE
    r.category_id = c.category_id
  ORDER BY
    r.created_at DESC
  FETCH FIRST 3 ROWS ONLY -- Oracle構文
) AS r_latest ON true;

このSQLにより、カテゴリごとに最新のレビューを取得しつつ、レビューが0件のカテゴリもリストから漏れることがありません。

シナリオ2:金融システム - 各顧客の直近の取引履歴を5件取得

要件: 顧客管理画面で、特定の顧客情報と共に、その顧客が行った直近5回の取引履歴(入出金など)を素早く表示したい。

この場合も、新規登録したばかりで取引履歴がまだない顧客を考慮して OUTER APPLY を使うのが堅牢な設計と言えます。

-- SQL Server
SELECT
  c.customer_name,
  t_recent.transaction_date,
  t_recent.amount
FROM
  customers AS c
OUTER APPLY (
  SELECT
    t.transaction_date,
    t.amount
  FROM
    transactions AS t
  WHERE
    t.customer_id = c.customer_id
  ORDER BY
    t.transaction_date DESC
  OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY
) AS t_recent;

データベーススペシャリスト午後試験での出題イメージ

データベーススペシャリスト試験、特に午後Ⅰ・午後Ⅱでは、LATERAL JOINAPPLY の知識が直接問われるSQL記述問題が頻出します。

設問例:

「各支店(支店マスタ)について、その支店で受注した最新3件の受注情報(受注伝票)を、支店名、受注日、受注金額の形式で取得するSELECT文を答えよ。なお、受注が1件もない支店も支店名を表示すること。」

解答への思考プロセス:

  1. 主テーブルの特定: まず、「各支店について」とあるので、FROM句の主役は支店マスタだと判断します。
  2. 行ごと処理の発想: 「各支店ごとに」「最新3件」というキーワードから、GROUP BYでは解けないトップN問題であると見抜きます。ここで行ごとのループ処理が可能な LATERAL JOIN / APPLY を使う方針を立てます。
  3. 外部結合の選択: 「受注が1件もない支店も表示」という要件から、内部結合系の CROSS APPLYJOIN LATERAL ではなく、外部結合系の OUTER APPLY または LEFT JOIN LATERAL を選択します。
  4. サブクエリの構築: APPLY句(またはLATERAL句)の中で、受注伝票テーブルを対象とし、WHERE句で支店マスタ.支店コード = 受注伝票.支店コードという相関条件を記述します。
  5. 順序と件数: ORDER BY 受注日 DESC で新しい順に並べ替え、FETCH FIRST 3 ROWS ONLY などで件数を絞り込みます。

この思考プロセスを身につけることで、複雑に見える問題文も構造的に分解し、正解のSQLをスムーズに導き出せるようになります。


まとめ│「行ごとのループ処理」という武器を手に入れる

本記事では、LATERAL JOINAPPLY 句について、その必要性から具体的な処理フロー、そして実践的な活用シナリオまでを解説しました。最後に、重要なポイントを振り返りましょう。

  • 核心的なイメージ: LATERAL JOINAPPLY の本質は、「主テーブルの各行をループの起点とし、その行の値をパラメータとしてサブクエリを実行する」という考え方です。この「行ごとのループ処理」のイメージが理解の鍵となります。
  • 存在理由: 従来のJOINGROUP BY、相関サブクエリでは困難だった「グループごとのトップN件」のような複雑な問い合わせを、直感的かつシンプルに記述するために生まれました。
  • 構文の使い分け:
    • LATERAL JOIN (PostgreSQL, Oracle等): 標準SQLに近い構文です。
    • APPLY (SQL Server等): CROSS APPLYOUTER APPLY があります。
  • 結合方法の選択:
    • サブクエリから対応するデータが見つからない行を除外したい場合 → JOIN LATERAL または CROSS APPLY (INNER JOIN相当)
    • データが見つからなくても主テーブルの行は残したい場合 → LEFT JOIN LATERAL または OUTER APPLY (LEFT JOIN相当)

データベーススペシャリスト試験の午後問題や、実務で複雑なデータを扱う際、「〇〇ごとに最新の△△を N 件…」といった要件が出てきたら、真っ先に LATERAL JOINAPPLY を思い出せるように訓練しておくことが、合格への、そしてスキルアップへの近道です。

この強力な機能を使いこなし、SQLによるデータ操作の可能性をさらに広げていきましょう。

-IPA|情報処理技術者試験