1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【図解】PostgreSQLのウィンドウ関数入門 ─ PARTITION BY・OVERを完全理解!

Last updated at Posted at 2025-12-25

はじめに

SQLを学んでいると、こんな場面に出くわしませんか?

  • 「部署ごとの合計を出したいけど、元のデータも残したい...」
  • 「売上ランキングを作りたいけど、GROUP BYだと行がまとまっちゃう...」
  • 「前日との差分を計算したい...」

こんな悩みを解決してくれるのが ウィンドウ関数 です!

この記事では、PostgreSQLのウィンドウ関数について、図解を交えながら初心者向けにわかりやすく解説します。

対象読者

  • SQLの基本(SELECT, WHERE, GROUP BY)は理解している
  • PARTITION BYOVER を見たことはあるけど、よくわからない
  • ウィンドウ関数を実務で使えるようになりたい

目次

  1. ウィンドウ関数とは?
  2. GROUP BY との違い
  3. 基本構文を理解しよう
  4. 実践!よく使うウィンドウ関数7選
  5. 実務でよくある使用例
  6. まとめ

ウィンドウ関数とは?

ウィンドウ関数とは、テーブルの各行に対して、関連する行のグループ(=ウィンドウ)を使って計算を行う関数です。

ポイントは 「行を集約せずに、計算結果を各行に追加できる」 ことです。

イメージ図

┌─────────────────────────────────────────────────────┐
│  通常の集約関数(SUM + GROUP BY)                    │
│                                                     │
│  行1 ─┐                                             │
│  行2 ─┼─→ まとめて1行に集約 → 結果: 1行            │
│  行3 ─┘                                             │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│  ウィンドウ関数(SUM + OVER)                        │
│                                                     │
│  行1 → 行1 + 計算結果                               │
│  行2 → 行2 + 計算結果   → 結果: 3行(元のまま!)   │
│  行3 → 行3 + 計算結果                               │
└─────────────────────────────────────────────────────┘

GROUP BY との違い

実際のデータで比較してみましょう。

サンプルデータ

CREATE TABLE sales (
    id SERIAL PRIMARY KEY,
    department VARCHAR(50),
    employee VARCHAR(50),
    amount INTEGER
);

INSERT INTO sales (department, employee, amount) VALUES
    ('営業', '田中', 100),
    ('営業', '佐藤', 150),
    ('営業', '山田', 200),
    ('開発', '鈴木', 180),
    ('開発', '高橋', 120);
id department employee amount
1 営業 田中 100
2 営業 佐藤 150
3 営業 山田 200
4 開発 鈴木 180
5 開発 高橋 120

GROUP BY の場合

SELECT 
    department,
    SUM(amount) AS total
FROM sales
GROUP BY department;

結果:2行に集約される

department total
営業 450
開発 300

ウィンドウ関数の場合

SELECT 
    department,
    employee,
    amount,
    SUM(amount) OVER (PARTITION BY department) AS dept_total
FROM sales;

結果:5行のまま、各行に部署合計が追加される

department employee amount dept_total
営業 田中 100 450
営業 佐藤 150 450
営業 山田 200 450
開発 鈴木 180 300
開発 高橋 120 300

ウィンドウ関数を使うと「個人の売上」と「部署の合計」を同時に表示できます!


基本構文を理解しよう

構文

関数名() OVER (
    [PARTITION BY カラム名]
    [ORDER BY カラム名]
    [フレーム句]
)

各要素の説明

要素 必須 説明
OVER() ウィンドウ関数であることを示す。空でもOK
PARTITION BY データをグループ分けする(省略時は全行が1グループ)
ORDER BY ウィンドウ内での順序を指定
フレーム句 計算対象の範囲を細かく指定(後述)

PARTITION BY のイメージ

PARTITION BY department を指定すると...

┌──────────────────┐    ┌──────────────────┐
│ 営業グループ     │    │ 開発グループ      │
│ ├─ 田中: 100    │    │ ├─ 鈴木: 180     │
│ ├─ 佐藤: 150    │    │ └─ 高橋: 120     │
│ └─ 山田: 200    │    │                  │
│                  │    │                  │
│ → 合計: 450     │    │ → 合計: 300      │
└──────────────────┘    └──────────────────┘

各グループ(パーティション)ごとに独立して計算される

ORDER BY のイメージ

ORDER BY を指定すると、累計のような計算ができます。

SUM(amount) OVER (ORDER BY id)
id=1: 100 → 累計: 100
id=2: 150 → 累計: 100 + 150 = 250
id=3: 200 → 累計: 100 + 150 + 200 = 450
id=4: 180 → 累計: 100 + 150 + 200 + 180 = 630
id=5: 120 → 累計: 100 + 150 + 200 + 180 + 120 = 750

PARTITION BYORDER BY は組み合わせて使うことも多いです!


実践!よく使うウィンドウ関数7選

1. ROW_NUMBER() - 連番を振る

各行に一意の連番を振ります。

SELECT 
    department,
    employee,
    amount,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY amount DESC) AS rank
FROM sales;
department employee amount rank
営業 山田 200 1
営業 佐藤 150 2
営業 田中 100 3
開発 鈴木 180 1
開発 高橋 120 2

ユースケース: 部署ごとの売上トップ3を取得する など

2. RANK() - 順位を付ける(同率あり・ギャップあり)

同じ値には同じ順位を付け、次の順位は飛ばします。

-- 同じ金額のデータを追加して試す
SELECT 
    employee,
    amount,
    RANK() OVER (ORDER BY amount DESC) AS rank
FROM sales;
山田: 200 → 1位
鈴木: 180 → 2位
佐藤: 150 → 3位
高橋: 120 → 4位
田中: 100 → 5位

もし佐藤と高橋が同じ150だったら...
山田: 200 → 1位
鈴木: 180 → 2位
佐藤: 150 → 3位  ← 同率
高橋: 150 → 3位  ← 同率
田中: 100 → 5位  ← 4位が飛ばされる!

3. DENSE_RANK() - 順位を付ける(同率あり・ギャップなし)

同じ値には同じ順位を付けますが、次の順位は飛ばしません。

山田: 200 → 1位
鈴木: 180 → 2位
佐藤: 150 → 3位  ← 同率
高橋: 150 → 3位  ← 同率
田中: 100 → 4位  ← 連続している!

4. LAG() - 前の行の値を取得

SELECT 
    employee,
    amount,
    LAG(amount, 1) OVER (ORDER BY id) AS prev_amount,
    amount - LAG(amount, 1) OVER (ORDER BY id) AS diff
FROM sales;
employee amount prev_amount diff
田中 100 NULL NULL
佐藤 150 100 50
山田 200 150 50
鈴木 180 200 -20
高橋 120 180 -60

ユースケース: 前日比、前月比の計算

LAG / LEAD の引数を詳しく解説

LAG()LEAD() は最大3つの引数を取ります。

LAG(カラム名, オフセット, デフォルト値) OVER (...)
LEAD(カラム名, オフセット, デフォルト値) OVER (...)
引数 必須 説明
カラム名 取得したいカラム
オフセット 何行前/後か(省略時は 1
デフォルト値 該当行がない場合の値(省略時は NULL

オフセットを変えた例:

SELECT 
    id,
    employee,
    amount,
    LAG(amount, 1) OVER (ORDER BY id) AS "1行前",
    LAG(amount, 2) OVER (ORDER BY id) AS "2行前",
    LAG(amount, 3) OVER (ORDER BY id) AS "3行前"
FROM sales;
id employee amount 1行前 2行前 3行前
1 田中 100 NULL NULL NULL
2 佐藤 150 100 NULL NULL
3 山田 200 150 100 NULL
4 鈴木 180 200 150 100
5 高橋 120 180 200 150

デフォルト値を指定する例:

前の行がない場合(最初の行など)は NULL になりますが、第3引数でデフォルト値を指定できます。

SELECT 
    id,
    amount,
    LAG(amount, 1, 0) OVER (ORDER BY id) AS prev_amount
FROM sales;
id amount prev_amount
1 100 0
2 150 100
3 200 150

デフォルト値を指定すると NULL の代わりにその値が入るので、計算時のエラーを防げます!

省略した場合:

第2引数を省略すると、自動的に 1 が使われます。

-- この2つは同じ意味
LAG(amount) OVER (ORDER BY id)
LAG(amount, 1) OVER (ORDER BY id)

1行前/後を取りたいだけなら省略してもOKです。

5. LEAD() - 次の行の値を取得

LAG() が「前の行」なら、LEAD() は「次の行」を取得します。引数の使い方は LAG() と同じです。

SELECT 
    employee,
    amount,
    LEAD(amount, 1) OVER (ORDER BY id) AS next_amount
FROM sales;
employee amount next_amount
田中 100 150
佐藤 150 200
山田 200 180
鈴木 180 120
高橋 120 NULL

6. FIRST_VALUE() / LAST_VALUE() - 最初・最後の値

SELECT 
    department,
    employee,
    amount,
    FIRST_VALUE(employee) OVER (
        PARTITION BY department 
        ORDER BY amount DESC
    ) AS top_performer
FROM sales;
department employee amount top_performer
営業 山田 200 山田
営業 佐藤 150 山田
営業 田中 100 山田
開発 鈴木 180 鈴木
開発 高橋 120 鈴木

7. SUM() / AVG() / COUNT() をウィンドウ関数として使う

通常の集約関数も OVER を付けるとウィンドウ関数になります!

SELECT 
    department,
    employee,
    amount,
    SUM(amount) OVER (PARTITION BY department) AS dept_total,
    AVG(amount) OVER (PARTITION BY department) AS dept_avg,
    COUNT(*) OVER (PARTITION BY department) AS dept_count,
    -- 累計
    SUM(amount) OVER (ORDER BY id) AS running_total
FROM sales;

実務でよくある使用例

ユースケース1: 部署ごとの売上トップ3を取得

WITH ranked AS (
    SELECT 
        department,
        employee,
        amount,
        ROW_NUMBER() OVER (
            PARTITION BY department 
            ORDER BY amount DESC
        ) AS rank
    FROM sales
)
SELECT * FROM ranked WHERE rank <= 3;

ROW_NUMBER() を使って順位を付け、CTEやサブクエリでフィルタリングするのが定番パターンです。

ユースケース2: 前月比を計算する

SELECT 
    month,
    revenue,
    LAG(revenue, 1) OVER (ORDER BY month) AS prev_month,
    ROUND(
        (revenue - LAG(revenue, 1) OVER (ORDER BY month)) * 100.0 
        / LAG(revenue, 1) OVER (ORDER BY month), 
        1
    ) AS growth_rate
FROM monthly_sales;

ユースケース3: 累計売上と達成率

SELECT 
    date,
    amount,
    SUM(amount) OVER (ORDER BY date) AS cumulative,
    ROUND(
        SUM(amount) OVER (ORDER BY date) * 100.0 / 1000000, 
        1
    ) AS achievement_rate
FROM daily_sales;

ユースケース4: 移動平均(直近3件の平均)

SELECT 
    date,
    amount,
    AVG(amount) OVER (
        ORDER BY date 
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3
FROM daily_sales;

ROWS BETWEEN はフレーム句と呼ばれ、計算対象の範囲を細かく指定できます。


よくあるエラーと対処法

エラー1: OVER() を付け忘れる

-- ❌ エラー
SELECT ROW_NUMBER() FROM sales;

-- ✅ 正しい
SELECT ROW_NUMBER() OVER () FROM sales;

エラー2: ウィンドウ関数を WHERE で使おうとする

-- ❌ エラー
SELECT * FROM sales
WHERE ROW_NUMBER() OVER (ORDER BY amount DESC) <= 3;

-- ✅ 正しい(サブクエリやCTEを使う)
WITH ranked AS (
    SELECT *, ROW_NUMBER() OVER (ORDER BY amount DESC) AS rn
    FROM sales
)
SELECT * FROM ranked WHERE rn <= 3;

ウィンドウ関数は SELECT と ORDER BY でしか使えません!
WHERE や HAVING では使えないので、CTEやサブクエリで囲みましょう。


まとめ

ウィンドウ関数のポイント

  1. 行を集約せずに計算結果を追加できる
  2. OVER() がウィンドウ関数の目印
  3. PARTITION BY でグループ分け、ORDER BY で順序指定
  4. WHERE では使えないのでCTE/サブクエリを活用

よく使う関数チートシート

関数 用途
ROW_NUMBER() 連番を振る
RANK() 順位(同率でギャップあり)
DENSE_RANK() 順位(同率でギャップなし)
LAG(col, n) n行前の値
LEAD(col, n) n行後の値
FIRST_VALUE() 最初の値
SUM/AVG/COUNT() OVER() 集約しない集計

おすすめの学習方法

  1. まずは ROW_NUMBER()PARTITION BY を使いこなす
  2. 次に LAG() で前後比較をマスター
  3. 実務で「これウィンドウ関数で書けるかも?」と意識する

参考リンク


最後まで読んでいただきありがとうございました!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?