SQLを学習していると、多くの人が「ウィンドウ関数」の壁に突き当たります。「OVER句って何のためにあるの?」「WINDOW句とWITH句はどう違うの?」といった疑問から、PARTITION BYやORDER BYが絡み合った複雑な構文に混乱してしまうケースは少なくありません。特に、応用情報技術者試験やデータベーススペ-シャリスト試験では、このウィンドウ関数を理解しているかどうかが、長文SQL問題を解くカギとなります。
GROUP BY句のように元々の行を集約してしまうことなく、各行の情報を保持したまま集計や順位付けができるのが、ウィンドウ関数の最大の強みです。しかし、その強力な機能と引き換えに、構文が少し複雑に見えるのも事実。
この記事では、そんなウィンドウ関数を「どの句が」「どの位置で」「何の役割を果たすのか」という3つのポイントに絞って、徹底的に分解・整理します。豊富な図解と具体的なSQLコード例を通して、それぞれの構文が持つ意味と正しい位置関係を体系的に理解し、苦手意識を払拭しましょう。
目次
- 1 SQLのウィンドウ関数とは?GROUP BYによる集計との違いを徹底解説
- 2 OVER句の役割と基本構文│PARTITION BYとORDER BYを使いこなす
- 3 🧩 OVER(PARTITION BY ~ ORDER BY ~)の集計の流れ
- 4 🧩 1. ウィンドウ関数の3タイプ分類
- 5 SQLのウィンドウ定義とは?OVER句の中身を3つの要素に分解
- 6 🧩 基本の考え方:OVER句の中のORDER BYは「先頭→現在行」の累計
- 7 SQLのWINDOW句とは?ウィンドウ定義の繰り返しを避けてSQLを効率化
- 8 【応用情報頻出】WINDOW句とWITH句の違いとは?役割と構文位置で比較
- 9 SQL構文の記述順序と実行順序│WITH・WINDOW句の位置関係まとめ
- 10 覚えるのは3つだけ!WITH, WINDOW, OVER句の役割と構文上の位置
- 11 試験対策メモ│応用情報・DBスペシャリストで狙われる頻出パターン
SQLのウィンドウ関数とは?GROUP BYによる集計との違いを徹底解説
ウィンドウ関数とは、一言でいえば「元となる表の行数を変えずに、集計や順位付けの結果を各行に追加できる関数」です。「分析関数」や「順序関数」とも呼ばれ、特にデータ分析や複雑なレポート作成の場面で絶大な効果を発揮します。
通常の集計関数(GROUP BY)との決定的な違い
ウィンドウ関数の便利さを理解するために、まずはSUM()のような通常の集計関数と比較してみましょう。
例えば、以下のような従業員テーブルemployeesがあるとします。
| employee_id | employee_name | department | salary |
|---|---|---|---|
| 101 | 佐藤 | 営業部 | 500 |
| 102 | 鈴木 | 営業部 | 600 |
| 103 | 高橋 | 開発部 | 550 |
| 104 | 田中 | 開発部 | 700 |
| 105 | 伊藤 | 開発部 | 650 |
ここで「部署ごとの給与合計」を計算する場合、多くの人はGROUP BYを使った以下のSQLを思い浮かべるでしょう。
SELECT
department,
SUM(salary) AS total_salary
FROM
employees
GROUP BY
department;
このSQLの結果は以下のようになります。
| department | total_salary |
|---|---|
| 営業部 | 1100 |
| 開発部 | 1900 |
ご覧の通り、GROUP BYを使うと、各部署の情報が1行に集約され、個々の従業員のレコードは結果から消えてしまいます。これは「要約レポート」を作る際には便利ですが、「個々の従業員の給与が、部署全体の合計に対してどのくらいの割合か」といった分析は一度ではできません。
ウィンドウ関数なら「行を消さずに」集計できる
一方、ウィンドウ関数SUM() OVER()を使うと、元の行をすべて残したまま、部署ごとの給与合計を「新しい列」として追加できます。
SELECT
employee_name,
department,
salary,
SUM(salary) OVER (PARTITION BY department) AS department_total_salary
FROM
employees;
このSQLの結果は以下のようになります。
| employee_name | department | salary | department_total_salary |
|---|---|---|---|
| 佐藤 | 営業部 | 500 | 1100 |
| 鈴木 | 営業部 | 600 | 1100 |
| 高橋 | 開発部 | 550 | 1900 |
| 田中 | 開発部 | 700 | 1900 |
| 伊藤 | 開発部 | 650 | 1900 |
GROUP BYとは異なり、元の5行がすべて維持されていますね。その上で、各行に「自分が所属する部署の給与合計」が併記されました。これがウィンドウ関数の最大の特徴です。
この「行を消さずに集計できる」仕組みは、OVER (PARTITION BY ...)という構文が「どの範囲(=ウィンドウ)で集計するか」をSQLエンジンに指示しているために実現します。この例では、departmentごとに区切られた範囲(営業部の2名、開発部の3名)を一時的な計算範囲として合計値を算出し、その結果を範囲内の各行に返しているのです。
OVER句の役割と基本構文│PARTITION BYとORDER BYを使いこなす
前のセクションで登場したOVER句は、ウィンドウ関数を理解する上で最も重要なキーワードです。この句の役割は、集計関数や順位関数を「ウィンドウ関数」として機能させるための"起動スイッチ"と考えると分かりやすいでしょう。
SUM()やAVG()、RANK()といった関数は、それ単体ではウィンドウ関数として動作しません。後ろにOVER()が続くことで初めて、「元の行を消さずに追加列として計算結果を返す」という特殊な振る舞いを始めます。
そして、本当に重要なのはOVER()のカッコの中に何を書くかです。このカッコの中に記述する内容を「ウィンドウ定義」と呼び、「どの範囲で」「どの順序で」計算するかをSQLエンジンに細かく指示します。
OVER()の中に書ける主な要素は以下の3つです。
PARTITION BY <列名>
計算対象の行セットを、指定した列の値に基づいてグループ分け(パーティション分割)します。GROUP BYのグループ化と似ていますが、行を集約しない点が異なります。「どこで区切るか(仕切り)」を指定する役割です。ORDER BY <列名>
PARTITION BYで区切られたグループの中で、さらにどの列を基準にレコードを並べるかを指定します。これにより「先頭から現在行まで」といった順序の概念が生まれ、累計計算や順位付けが可能になります。「どの順番で計算するか(順序)」を指定する役割です。ROWS/RANGE/GROUPS句
ORDER BYで並べた後、さらに計算対象とする範囲を「現在行からN行前まで」のように、より動的に絞り込むための指定です。これは「フレーム句」と呼ばれ、移動平均の計算などで使われます。「どこからどこまでを対象にするか(範囲の絞り込み)」を指定する高度な役割です。
SQL具体例で学ぶOVER句の使い方
言葉だけでは分かりにくいので、具体的なSQLで使い方を見ていきましょう。
例1:グループごとの平均値を求める(PARTITION BY)
各従業員の給与と、その人が所属する「部署の平均給与」を並べて表示するケースです。
SELECT
employee_name,
department,
salary,
-- 部署ごとに区切って(PARTITION BY)、給与の平均を計算
AVG(salary) OVER (PARTITION BY department) AS department_avg_salary
FROM
employees;
結果
| employee_name | department | salary | department_avg_salary |
|---|---|---|---|
| 佐藤 | 営業部 | 500 | 550 |
| 鈴木 | 営業部 | 600 | 550 |
| 高橋 | 開発部 | 550 | 633.3... |
| 田中 | 開発部 | 700 | 633.3... |
| 伊藤 | 開発部 | 650 | 633.3... |
例2:日付順の累計売上を求める(PARTITION BY + ORDER BY)
日々の売上記録salesテーブルから、日ごとの累計売上を計算するケースです。
-- sales テーブル
-- sale_date | amount
-- 2023-04-01 | 100
-- 2023-04-02 | 150
-- 2023-04-03 | 120
SELECT
sale_date,
amount,
-- 日付順に並べて(ORDER BY)、現在行までの売上を合計
SUM(amount) OVER (ORDER BY sale_date) AS cumulative_amount
FROM
sales;
結果
| sale_date | amount | cumulative_amount |
|---|---|---|
| 2023-04-01 | 100 | 100 |
| 2023-04-02 | 150 | 250 |
| 2023-04-03 | 120 | 370 |
例3:過去3日間の移動平均を求める(ORDER BY + ROWS句)
ORDER BYとフレーム句を使い、より複雑な「当日を含む過去3日間の売上移動平均」を計算します。
SELECT
sale_date,
amount,
-- 日付順に並べ、現在行と遡って2行前までの範囲で平均を計算
AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3days
FROM
sales;
結果
| sale_date | amount | moving_avg_3days |
|---|---|---|
| 2023-04-01 | 100 | 100 |
| 2023-04-02 | 150 | 125 |
| 2023-04-03 | 120 | 123.3... |
このように、OVER()の中身を使い分けることで、単純な集計から累計、移動平均まで、多彩な分析がSQLだけで可能になります。
結論から言うと:
OVER(PARTITION BY ~ ORDER BY ~) は、
「まずグループを区切り(PARTITION BY)、
その中で順番をつけて(ORDER BY)、
その順番に沿って集計する」
という 二段階構造 の考え方になります。
🧩 OVER(PARTITION BY ~ ORDER BY ~)の集計の流れ
OVER(PARTITION BY ~ ORDER BY ~) は、
「まずグループを区切り(PARTITION BY)、
その中で順番をつけて(ORDER BY)、
その順番に沿って集計する」
という 二段階構造 の考え方になります。
OVER(PARTITION BY 部署 ORDER BY 給料)
と書くと、実際の処理は以下のような2ステップで行われます。
🪜 ステップ①:PARTITION BY で“グループ”を作る
まず テーブル全体を「部署ごと」に分割します。
| 部署 | 社員 | 給料 |
|---|---|---|
| 営業 | 田中 | 400 |
| 営業 | 佐藤 | 300 |
| 開発 | 鈴木 | 500 |
| 開発 | 高橋 | 600 |
👉 部署 = 営業グループ と 開発グループ の2つに分かれる。
🪜 ステップ②:各グループの中で ORDER BY で並び替える
次に、各グループごとに指定した列(ここでは給料)で並び順をつける。
| 部署 | 社員 | 給料 | 並び順 |
|---|---|---|---|
| 営業 | 佐藤 | 300 | 1位 |
| 営業 | 田中 | 400 | 2位 |
| 開発 | 鈴木 | 500 | 1位 |
| 開発 | 高橋 | 600 | 2位 |
🪜 ステップ③:その順番に沿って集計・順位付け
例えば:
だと、「部署ごと」に「給料の昇順」で累計を計算します。
| 部署 | 社員 | 給料 | 部署内累計 |
|---|---|---|---|
| 営業 | 佐藤 | 300 | 300 |
| 営業 | 田中 | 400 | 700 |
| 開発 | 鈴木 | 500 | 500 |
| 開発 | 高橋 | 600 | 1100 |
→ 部署ごとの累計(running total within partition)
が出せる、ということです。
PARTITION BY が「グループ化」→ ORDER BY が「順番付け」
| 句 | 処理の単位 | 役割 |
|---|---|---|
PARTITION BY |
グループの単位を決める | 「どこまでが一つのまとまりか」 |
ORDER BY |
各グループ内の順番 | 「どう並べて集計を進めるか」 |
つまり:
「GROUP BY のように区切り(PARTITION)を作り、
その中で ORDER BY に沿ってウィンドウを動かす」
💡 3. 関数によって役割が少し変わる
| 関数 | PARTITION BY の影響 |
ORDER BY の影響 |
|---|---|---|
SUM() / AVG() |
グループを独立して集計 | 並び順に応じて累計・移動平均など |
RANK() / ROW_NUMBER() |
グループごとに順位をリセット | 並び順の基準を決定 |
LAG() / LEAD() |
グループをまたがない | 前後関係を定義(時間順など) |
🧠 4. 直感イメージ
-
PARTITION BYは「クラス分け」 -
ORDER BYは「席順を決める」 -
集計関数は「その順番で足し上げる/平均する」
🎓 つまり、
「部署(クラス)」ごとに、「給料(席順)」で並べて、
上から順に累計を出す。
🧩 1. ウィンドウ関数の3タイプ分類
ウィンドウ関数は大きく分けると3系統に分類されます。
| 分類 | 主な関数 | 処理の特徴 |
|---|---|---|
| ① 集計系(累計・平均) | SUM, AVG, COUNT, MAX, MIN |
過去から現在行までをまとめて集計する |
| ② 順位系(ランキング) | RANK, DENSE_RANK, ROW_NUMBER, NTILE |
並び順を基準に順位や行番号を付ける |
| ③ 参照系(前後比較) | LAG, LEAD, FIRST_VALUE, LAST_VALUE |
指定した範囲の他行の値を参照する |
これら3タイプで、PARTITION BY と ORDER BY の意味が少しずつ違うのです。
🧠 2. ① 集計系関数(SUM, AVGなど)
構文例
動作の流れ
-
PARTITION BY:店舗ごとに独立したグループを作る -
ORDER BY:日付順に並べる -
SUM():並び順に従って先頭から現在行まで累計
| 店舗 | 日付 | 売上 | 累計売上 |
|---|---|---|---|
| A | 1日 | 100 | 100 |
| A | 2日 | 200 | 300 |
| A | 3日 | 150 | 450 |
| B | 1日 | 50 | 50 |
| B | 2日 | 100 | 150 |
➡️ **「グループ内累計」や「移動平均」**などが得られます。PARTITION BY がグループを分け、ORDER BY が時間(順序)を与える。
🧮 3. ② 順位系関数(RANK, ROW_NUMBERなど)
構文例
動作の流れ
-
PARTITION BY:部署ごとに順位リセット -
ORDER BY:並び順(給料の高い順)を定義 -
RANK():その順番に順位を付ける
| 部署 | 社員 | 給料 | 順位 |
|---|---|---|---|
| 営業 | 田中 | 500 | 1 |
| 営業 | 佐藤 | 400 | 2 |
| 開発 | 鈴木 | 700 | 1 |
| 開発 | 高橋 | 600 | 2 |
➡️ PARTITION BY がリセット単位(部署ごとに1位から数え直す)ORDER BY が順位の基準列(降順なら高い順に1位)
メモ
-
RANK():同値に同順位をつける(次の順位が飛ぶ) -
DENSE_RANK():同値も連番で続く -
ROW_NUMBER():順位ではなく単純な連番(常に1,2,3...)
| 関数 | 2位が重複したとき | 結果例 |
|---|---|---|
| RANK() | 次が飛ぶ | 1, 2, 2, 4 |
| DENSE_RANK() | 飛ばさない | 1, 2, 2, 3 |
| ROW_NUMBER() | 常に連番 | 1, 2, 3, 4 |
🔁 4. ③ 参照系関数(LAG, LEAD, FIRST_VALUEなど)
構文例
動作の流れ
-
PARTITION BY:店舗ごとに独立した系列に分ける -
ORDER BY:日付順に並べて時間の流れを作る -
LAG():1行前の値を参照(前日)
| 店舗 | 日付 | 売上 | 前日売上 |
|---|---|---|---|
| A | 1日 | 100 | NULL |
| A | 2日 | 200 | 100 |
| A | 3日 | 150 | 200 |
➡️ ORDER BY が「前後関係」を定義する軸。PARTITION BY で別店舗を独立させて、A店の売上はB店と混ざらない。
🧩 5. それぞれの関数での句の働きを比較
| 種類 | 関数例 | PARTITION BY の役割 |
ORDER BY の役割 |
主な用途 |
|---|---|---|---|---|
| 集計系 | SUM, AVG | グループ単位で集計を独立 | 並び順で累計や移動平均を出す | 累計・移動平均 |
| 順位系 | RANK, ROW_NUMBER | グループごとに順位をリセット | 並び順で順位付け | ランキング |
| 参照系 | LAG, LEAD | 参照系列を分ける | 前後関係を定義 | 前回値・差分計算 |
一言でまとめると
PARTITION BY は「世界(集計・順位・系列)を分ける」。
ORDER BY は「その世界の中で時間や順位の軸を作る」。
集計系では「累計」、順位系では「並び順」、参照系では「前後関係」をそれぞれ制御している。
🧭 5. よくある実践パターン
| 用途 | 記述例 | 意味 |
|---|---|---|
| 部署別に給料順位を出す | RANK() OVER(PARTITION BY 部署 ORDER BY 給料 DESC) |
部署ごとに順位をリセットして給料順に並べる |
| 部署別に累計売上を出す | SUM(売上) OVER(PARTITION BY 部署 ORDER BY 日付) |
部署ごとに日付順で累積売上を出す |
| 支店別・日付順に直近3日の平均 | AVG(売上) OVER(PARTITION BY 支店 ORDER BY 日付 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) |
支店ごとに直近3日の移動平均 |
✅ まとめ
| 句 | 処理イメージ | 結果の粒度 |
|---|---|---|
PARTITION BY |
グループを区切る(部署・支店など) | グループごとに独立 |
ORDER BY |
各グループ内で順番を作る | 累計・順位など「順序ベース」の集計 |
| 両方使う | グループごとに順番を付けて集計 | 典型的なウィンドウ関数構文(最も多い) |
一言でいうと
PARTITION BY で「どこまでが一つの世界か」を決め、
その世界の中で ORDER BY に沿って「時間(順序)」を進めながら計算する。
SQLのウィンドウ定義とは?OVER句の中身を3つの要素に分解
前のセクションで、OVER()のカッコの中に書く内容が重要だと解説しました。このOVER()の中に記述するPARTITION BYやORDER BYなどのコード全体を「ウィンドウ定義 (Window Definition)」と呼びます。
ウィンドウ定義の役割は、計算対象となる行の範囲(=ウィンドウ)を正確に指定するための「設計図」です。この設計図をSQLエンジンに渡すことで、私たちは柔軟な集計や分析が可能になります。
重要なポイントは、このウィンドウ定義はSELECT句の中で、SUM()やRANK()といったウィンドウ関数の直後にOVER()として記述される、という構文上の位置です。
ウィンドウ定義を構成する3つの句
ウィンドウ定義、つまりOVER()のカッコの中身は、主に以下の3つの構成要素(句)から成り立っています。これらを組み合わせることで、計算範囲を自由にカスタマイズできます。
| 構成要素 | 役割 | 主要なキーワード |
|---|---|---|
| パーティション句 | データをグループに分割する(仕切り) | PARTITION BY |
| ORDER BY句 | グループ内のデータの順序を決める(整列) | ORDER BY |
| フレーム句 | 順序付けされたデータ内の範囲をさらに絞り込む | ROWS, RANGE, GROUPSBETWEEN ... AND ...PRECEDING (前)FOLLOWING (後)CURRENT ROW (現在行) |
PARTITION BYで大きなグループを作り、ORDER BYでその中の順番を決め、ROWS句(フレーム句)でさらに「自分から見て何行前まで」といった微調整を行うイメージです。
フル構文で読み解くウィンドウ定義
では、これら3つの要素をすべて使った複雑な例を見てみましょう。一見難しく見えますが、一つずつ分解すれば意味を理解できます。
シナリオ: 各従業員について「同じ部署内で、入社日が自分と近い前後1名ずつ(合計3名)の平均給与」を計算したい。
SELECT
employee_name,
department,
hire_date,
salary,
AVG(salary) OVER (
PARTITION BY department -- ① 部署でグループ分け
ORDER BY hire_date -- ② 入社日で並び替え
ROWS BETWEEN 1 PRECEDING -- ③ 1行前から
AND 1 FOLLOWING -- 1行後までを範囲とする
) AS avg_salary_around
FROM
employees;
このOVER()の中身(ウィンドウ定義)は、以下のように解釈されます。
PARTITION BY department: まず、全従業員を「部署」ごとに分割します。計算は部署内で完結し、他の部署のデータは影響しません。ORDER BY hire_date: 分割された部署の中で、従業員を「入社日」の早い順に並べます。ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING: 入社日順に並んだレコードに対し、計算範囲を「現在の行の1行前 (1 PRECEDING) から1行後 (1 FOLLOWING) まで」に限定します。(※先頭行の場合は自分と次の行、最終行の場合は自分と前の行が対象になります)AVG(salary) OVER(...): 最終的に、上記で指定された範囲(最大3行)に含まれるレコードのsalaryの平均値を計算し、avg_salary_around列として返します。
このように、ウィンドウ定義の各要素が持つ役割を理解することで、複雑な要件でもSQLでスマートに表現できるようになります。
🧩 基本の考え方:OVER句の中のORDER BYは「先頭→現在行」の累計
ORDER BY を指定した時点で、SQLは「先頭行から現在行まで」の範囲を自動的にウィンドウとして認識する。
その後に ROWS や RANGE を指定すると、
「そのウィンドウのどこからどこまでを集計するか(=範囲)」を細かく調整できる。
デフォルトでは「先頭から現在行まで」
このとき、SQLの内部では自動的に次のような範囲(frame)が設定されます:
意味は:
「最初の行(=UNBOUNDED PRECEDING)から今の行まで」
つまりこれが「累計(running total)」になります。
| 日付 | 売上 | 累計売上 |
|---|---|---|
| 1日 | 100 | 100 |
| 2日 | 200 | 300 |
| 3日 | 300 | 600 |
範囲を限定したいときに使うのが ROWS / RANGE
「全部じゃなくて、直近3行だけ」「現在行の前後1行だけ」などを集計したい場合👇
これで「過去2行+現在行」の平均を毎回計算する、いわゆる**移動平均(moving average)**になります。
| 日付 | 売上 | 移動平均 |
|---|---|---|
| 1日 | 100 | 100 |
| 2日 | 200 | 150 |
| 3日 | 300 | 200 |
| 4日 | 400 | 300 |
🧠 OVER句の中のORDER BYで使われる キーワードの関係まとめ
| キーワード | 意味 |
|---|---|
UNBOUNDED PRECEDING |
ウィンドウの先頭行(=最初の行) |
CURRENT ROW |
現在の行 |
UNBOUNDED FOLLOWING |
ウィンドウの末尾(=最後の行) |
<n> PRECEDING |
現在行の前n行まで |
<n> FOLLOWING |
現在行の後n行まで |
これを BETWEEN ~ AND ~ の形で組み合わせます👇
| 指定 | 意味 | 典型用途 |
|---|---|---|
BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
先頭から現在まで | 累計 |
BETWEEN 2 PRECEDING AND CURRENT ROW |
過去2行+現在行 | 移動平均 |
BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING |
現在から最後まで | 後方累積 |
BETWEEN 1 PRECEDING AND 1 FOLLOWING |
前後1行 | 平滑化など |
📈 OVER句の中のORDER BYをイメージで表すと
この「“ここまで”を毎回計算する」のが ORDER BY + OVER() の動き。
その「ここまで」の範囲を自由に調整するのが ROWS / RANGE です。
| 状況 | SQL記述 | 範囲の意味 | 結果 |
|---|---|---|---|
| 基本形 | SUM(売上) OVER(ORDER BY 日付) |
先頭~現在行 | 累計 |
| 範囲指定あり | AVG(売上) OVER(ORDER BY 日付 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) |
過去2行+現在行 | 移動平均 |
| 明示的に全体 | SUM(売上) OVER() |
全体固定 | 全体合計 |
一言でまとめると
ORDER BY が「順番」を作り、
デフォルトでは先頭から現在行までを集計。
そこに ROWS / RANGE を加えると「集計範囲」を細かく操れる。
SQLのWINDOW句とは?ウィンドウ定義の繰り返しを避けてSQLを効率化
複雑なデータ分析を行うと、SELECT句の中で同じ内容のウィンドウ定義(OVER(...)の中身)を何度も記述する場面に遭遇します。例えば、「部署ごとの給与ランキング」と「部署ごとの給与累計」を同時に計算する場合、どちらもOVER (PARTITION BY department ORDER BY salary DESC)という全く同じ定義を使う必要があります。
このようなコードの繰り返しは、SQLを読みにくくするだけでなく、後で修正が必要になった際に複数の箇所を直さなければならないため、ミスの原因にもなります。
この問題を解決するのがWINDOW句です。WINDOW句は、ウィンドウ定義に名前(エイリアス)を付け、それを使い回すための機能です。プログラミングにおける「変数」のように、一度定義すれば何度でも名前で呼び出せるため、SQLを非常にスッキリさせることができます。
WINDOW句の構文ルールと「位置」
WINDOW句を理解する上で最も重要なのは、その「記述する位置」です。
OVER()句がSELECT句の中に書かれるのに対し、WINDOW句はFROM句やWHERE句よりも後、ORDER BY句よりも前に記述します。
使い方はシンプルです。
- SQL文の後半(
FROM句やWHERE句の後)に、WINDOW <名前> AS (<ウィンドウ定義>)という形式でウィンドウ定義に名前を付けます。 SELECT句の中では、OVER <名前>という形式で、定義した名前を呼び出します。
WINDOW句を使ったSQLの比較(Before/After)
シナリオ: 部署ごとに給与の高い順で「給与ランキング」と「累計給与額」を算出する。
Before: WINDOW句を使わない場合
同じOVER(...)の記述が2回登場し、冗長です。
SELECT
employee_name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk,
SUM(salary) OVER (PARTITION BY department ORDER BY salary DESC) AS cumulative_salary
FROM
employees;
After: WINDOW句を使った場合
WINDOW句でdep_windowという名前を定義し、SELECT句から2回呼び出しています。コードが簡潔で、意図も明確になりました。
SELECT
employee_name,
department,
salary,
RANK() OVER dep_window AS rnk,
SUM(salary) OVER dep_window AS cumulative_salary
FROM
employees
WINDOW dep_window AS (PARTITION BY department ORDER BY salary DESC);
このようにWINDOW句は、特に応用情報やDBスペシャリスト試験の長文SQL問題で、複雑なクエリの可読性を上げるために使われることがあります。「FROM句の後にWINDOWというキーワードが出てきたら、ウィンドウ定義に名前を付けているんだな」と瞬時に判断できるようにしておきましょう。
【応用情報頻出】WINDOW句とWITH句の違いとは?役割と構文位置で比較
WINDOW句を学習すると、必ずと言っていいほど「WITH句と何が違うの?」という疑問が生まれます。どちらもWから始まるキーワードで、何かを定義して再利用する、という点で非常に似ているため、混同しやすいのも無理はありません。
しかし、この2つは役割も記述する位置も全く異なります。応用情報技術者試験やDBスペシャリスト試験では、この違いを理解していることが長文SQL読解の前提となるため、ここで明確に区別しておきましょう。
結論から言うと、違いは「何に名前を付けて再利用するか」です。
WITH句:テーブル(SELECT文の実行結果)に名前を付けるWINDOW句:ウィンドウ定義(OVER()の中身)に名前を付ける
WITH句:テーブルに名前を付ける(共通テーブル式)
WITH句は「共通テーブル式(Common Table Expressions, CTE)」とも呼ばれ、SELECT文によって得られる結果セット(つまり、一時的なテーブル)に名前を与える機能です。
複雑なSQLをいくつかの単純な処理ブロックに分割し、それぞれに名前を付けて組み立てていくようなイメージです。これにより、サブクエリが何重にもネスト(入れ子)になるのを防ぎ、SQL全体の可読性を大幅に向上させます。
最も重要な特徴は、WITH句は必ずSQL文の先頭に記述されるという点です。
-- ① WITH句で「sales_summary」という名前の一時テーブルを定義
WITH sales_summary AS (
SELECT
department,
SUM(sales) AS total_sales
FROM
sales_data
GROUP BY
department
)
-- ② 定義した「sales_summary」を通常のテーブルのように使ってSELECTする
SELECT
*
FROM
sales_summary
WHERE
total_sales > 10000;
WINDOW句:ウィンドウ定義に名前を付ける
一方、WINDOW句は、これまで見てきた通り、OVER(PARTITION BY ... ORDER BY ...)というウィンドウ定義に名前を与える機能です。
これはあくまでSELECT句の中で使うOVER句の記述を効率化・共通化するためのものであり、新しいテーブルを作り出すわけではありません。そして、WINDOW句はFROM句やWHERE句の後ろに記述されます。
一目でわかる比較まとめ表
WITH句とWINDOW句の違いを、以下の表にまとめます。
| 比較項目 | WITH句(共通テーブル式) |
WINDOW句 |
|---|---|---|
| 目的 | テーブル(SELECT文の結果)の再利用 |
ウィンドウ定義(OVER()の中身)の再利用 |
| 記述位置 | SQL文の先頭 | FROM句やWHERE句の後 |
| 構文例 | WITH temp AS (...) SELECT ... |
SELECT ... OVER w ... WINDOW w AS (...) |
| 主な役割 | 複雑なクエリの分割、可読性の向上 | 同じ集計範囲・順序設定の使い回し、冗長性の排除 |
この2つは全くの別物です。「WITHはSQLの最初に書く、テーブルの別名」「WINDOWはSQLの途中に書く、OVER句設定の別名」と、位置と役割をセットで覚えておきましょう。
SQL構文の記述順序と実行順序│WITH・WINDOW句の位置関係まとめ
ここまでWITH句、WINDOW句、そしてOVER()句内のウィンドウ定義と、様々な構文要素が登場しました。これらを組み合わせたSELECT文全体を書くとき、各句の「記述する順番」には厳格なルールが存在します。
この順序を正しく理解し、覚えることは、SQLを正確に書くための絶対条件です。特に試験問題では、構文の正しさが直接問われることもあります。
SQL構文の正しい「記述順序」
私たちがSQLを書く際の正しい句の順序は、以下の通りです。[]で囲まれた句は省略可能です。
-- 私たちがSQLを書くときの正しい順番
WITH ...
SELECT [DISTINCT] ... [OVER (...)]
FROM ...
[WHERE ...]
[GROUP BY ...]
[HAVING ...]
[WINDOW ...]
[ORDER BY ...]
[LIMIT ...];
この順番は鉄則です。例えば、WHERE句をFROM句の前に書いたり、ORDER BY句をWINDOW句の前に書くことはできず、エラーとなります。WINDOW句がGROUP BYやHAVINGの後、ORDER BYの前に位置することを改めて確認してください。
重要:記述順とデータベース内部の「論理的な実行順序」は異なる
ここで非常に重要なポイントがあります。それは、私たちが上記のように記述する順序と、データベースシステムが内部でSQLを解釈して処理を実行する順序(論理実行順序)は異なる、という点です。
「SELECT句で付けた列の別名(エイリアス)を、なぜWHERE句で使えないの?」という誰もが一度は抱く疑問の答えは、この実行順序に隠されています。
データベース内部での、大まかな論理的な実行順序は以下のようになっています。
FROM: まず、操作対象のテーブルを確定します。WHERE: 次に、WHERE句の条件に基づいて、テーブルから必要な行だけを絞り込みます。GROUP BY:GROUP BY句の指定に従って、絞り込んだ行をグループ化します。HAVING: グループ化した結果に対して、HAVING句の条件でさらに絞り込みます。SELECT: ここで初めて、SELECT句に指定された列が評価されます。ウィンドウ関数(OVER)の計算もこの段階で実行されます。ORDER BY:SELECT句で作り出された最終的な結果セットを、指定された順序に並べ替えます。LIMIT: 並べ替えた結果から、表示する行数を制限します。
WHERE句でSELECT句の別名が使えないのは、実行順序としてWHERE句の処理(ステップ2)がSELECT句の処理(ステップ5)よりも先に行われるためです。WHERE句が処理される時点では、まだその別名は存在していないのです。
また、WITH句はこれらのメインクエリが実行される前の準備段階として、最初に一時テーブルを作成します。WINDOW句は、ステップ5のウィンドウ関数計算時に参照される定義情報、と理解すると良いでしょう。
覚えるのは3つだけ!WITH, WINDOW, OVER句の役割と構文上の位置
ここまで多くのキーワードと構文ルールを解説してきましたが、頭が混乱しないように、最後に覚えるべき核心を3つのポイントに絞って整理します。この3つの句の「目的」と「位置」の違いさえ押さえれば、複雑なSQLも怖くありません。
✅ Point 1: OVER句は「ウィンドウ定義」そのもの
ウィンドウ関数の心臓部であり、計算ルールを直接指定するのがOVER句です。これは必ずSELECT句の中で、SUM()やRANK()といった関数の直後に書きます。PARTITION BYやORDER BYといった具体的な計算範囲の設計(ウィンドウ定義)は、すべてこのOVER()のカッコの中に記述されます。
✅ Point 2: WINDOW句は「ウィンドウ定義の再利用」
同じウィンドウ定義(OVER句の中身)を何度も書くのを避けるための便利機能がWINDOW句です。記述する位置はFROM句やWHERE句の後。WINDOW w AS (...)のように定義に名前を付け、SELECT句からはOVER wのようにシンプルな名前で呼び出します。
✅ Point 3: WITH句は「テーブルの再利用」
WINDOW句と混同しがちですが、WITH句が再利用するのは「テーブル(SELECT文の実行結果)」そのものです。SQL全体の可読性を上げるための強力な機能で、記述する位置は必ずSQL文の先頭になります。
この3つの関係性は「WITH句で準備したテーブルを使い、WINDOW句で定義した計算ルールを、SELECT句の中のOVER句が呼び出して実行する」とイメージすると、全体像が掴みやすくなるでしょう。
試験対策メモ│応用情報・DBスペシャリストで狙われる頻出パターン
これまで学んできたウィンドウ関数は、応用情報技術者試験(AP)やデータベーススペシャリスト試験(DB)の午後問題、特に長文SQLの読解問題で頻出のテーマです。GROUP BYだけでは実現できない複雑な集計や順位付けが可能なため、受験者のSQLに対する深い理解度を試す上で格好の題材だからです。
ここでは、実際の試験でどのような点が狙われやすいのか、対策のポイントを解説します。
頻出出題パターン3選
パターン1:「OVER句の中身」を問う問題
最も王道なのが、問題文で示された要件(例:「各支店において、取扱商品の価格が高い順に社内管理コードを付与する」)を実現するために、OVER(PARTITION BY ??? ORDER BY ???)の???部分にどの列名が入るかを答えさせる問題です。
「~ごとに」という日本語はPARTITION BYに、「~の順で」はORDER BYに、それぞれ対応します。この翻訳を素早く正確に行えるかが鍵となります。
パターン2:「構文の正しさ」を問う問題
複数のSQL文の選択肢の中から、文法的に正しいものを一つ選ばせる形式の問題です。このタイプの問題では、本記事で解説した「SQLの記述順序」の知識がそのまま武器になります。
例えば、WINDOW句がGROUP BY句より前に記述されていたり、WITH句がSELECT文の途中に登場したりする選択肢は、内容を詳しく読まなくても構文エラーとして瞬時に除外できます。
パターン3:「GROUP BYとの違い」を問う問題
設問が要求している最終的なアウトプットの形式から、GROUP BYとウィンドウ関数のどちらを使うべきかを判断させる問題です。
「全社員のリストに、各社員が所属する部署の平均年齢を"併記"する」のように、元の行情報を残す必要がある場合はウィンドウ関数です。一方で、「部署ごとの平均年齢リストを作成する」のように、結果を集約・要約する必要がある場合はGROUP BYが適切です。この違いを見抜く力が試されます。
長文SQL読解のコツ
試験本番で長く複雑なSQLに直面しても、決して焦らないでください。まずはWITH、FROM、WHERE、SELECTといった大きな構文のブロックごとに、何が行われているかを把握するのが鉄則です。
特にウィンドウ関数が出てきたら、「SELECT → FROM → WINDOW → ORDER BY」という基本構造を常に念頭に置き、一つひとつの句が持つ役割を冷静に当てはめていくことで、必ず正しく処理を追うことができます。