そろそろウィンドウ関数を使うときにハマりがちなポイントについて話そうと思う。プログラミング(てか人生もだけど)では、他人のミスから学ぶのが一番効率いいよね。ここでは初心者がやりがちなミス、時にはベテランでもやっちゃうミスを取り上げて、どうやって避けるか一緒に見ていこう。
ミスその1: PARTITION BYの使い方ミス
一番ありがちなのが、PARTITION BYを忘れたり、間違った指定をしちゃうこと。特にデータをグループごとに分けたいときにやりがち。これがないと、PostgreSQLは全行を一つの大きなグループとして扱っちゃうから、思った通りの結果にならないことが多い。
例えば、salesっていう売上データのテーブルがあるとする:
| id | region | month | total |
|---|---|---|---|
| 1 | North | 2023-01 | 1000 |
| 2 | South | 2023-01 | 800 |
| 3 | North | 2023-02 | 1200 |
| 4 | South | 2023-02 | 900 |
各地域ごとに月ごとの累積売上(SUM())を出したいとき、こんなクエリを書きがち:
SELECT
region,
month,
SUM(total) OVER (ORDER BY month) AS running_total
FROM
sales;
結果:
| region | month | running_total |
|---|---|---|
| North | 2023-01 | 1000 |
| South | 2023-01 | 1800 |
| North | 2023-02 | 3000 |
| South | 2023-02 | 3900 |
パッと見は良さそうだけど、実は期待した結果じゃない。累積合計が地域ごとじゃなくて、全体で計算されてる。これはPARTITION BY regionを忘れてるせい。
修正版コード:
SELECT
region,
month,
SUM(total) OVER (PARTITION BY region ORDER BY month) AS running_total
FROM
sales;
結果:
| region | month | running_total |
|---|---|---|
| North | 2023-01 | 1000 |
| North | 2023-02 | 2200 |
| South | 2023-01 | 800 |
| South | 2023-02 | 1700 |
これでちゃんと地域ごとにグループ化されて、累積合計もそれぞれ計算されるようになった!
ミスその2: ORDER BYの順序ミス
OVER()の中のORDER BYは、ウィンドウ内での行の順番を決める。ここを間違えると、思わぬ結果になることがある。
例えば、月ごとの売上累積を降順で出したいとき、こんなクエリを書いたとする:
SELECT
month,
total,
SUM(total) OVER (ORDER BY month DESC) AS running_total
FROM
sales;
結果:
| month | total | running_total |
|---|---|---|
| 2023-02 | 1200 | 1200 |
| 2023-02 | 900 | 2100 |
| 2023-01 | 1000 | 3100 |
| 2023-01 | 800 | 3900 |
一見正しそうだけど、よく見ると月ごとにグループ化されてるだけで、累積合計が変な感じになってる。これは降順にしたせいで、計算の順番が変わっちゃってるから。
修正方法: ORDER BYを正しい順序(昇順)に直そう:
SELECT
month,
total,
SUM(total) OVER (ORDER BY month ASC) AS running_total
FROM
sales;
ミスその3: インデックスなしでウィンドウ関数を使う
ウィンドウ関数は大量データを扱うことが多いから、キーとなるカラムにインデックスがないと、パフォーマンスが激落ちすることもある。
例: large_salesっていう何百万行もあるテーブルで、売上のランクを計算したいとする:
SELECT
id,
total,
RANK() OVER (ORDER BY total DESC) AS rank
FROM
large_sales;
データが少なければすぐ終わるけど、量が多いとめっちゃ時間かかる。
修正方法:ORDER BYで使うカラムにインデックスを貼ろう:
CREATE INDEX idx_total ON large_sales(total DESC);
これでクエリの実行がかなり速くなるよ。
ミスその4: ROWSやRANGEの意味を勘違いする
ROWSやRANGEを使うときは、それぞれがどうやってウィンドウを決めるかちゃんと理解しておくのが大事。ここを勘違いすると、思わぬ結果になる。
例: 今月とその前2ヶ月の移動平均を出したいとき:
SELECT
month,
AVG(total) OVER (ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM
sales;
もしROWSの代わりにRANGEを使うと:
SELECT
month,
AVG(total) OVER (ORDER BY month RANGE BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM
sales;
結果が変わる。なぜならRANGEは値の範囲でウィンドウを決めるけど、ROWSは行数で決めるから。
ミスその5: ウィンドウ関数の使いすぎ
1つのクエリでウィンドウ関数を何回も使うと、計算が重複してパフォーマンスが落ちることがある。
例:
SELECT
id,
total,
SUM(total) OVER (PARTITION BY region) AS region_total,
SUM(total) OVER (PARTITION BY region) / COUNT(total) OVER (PARTITION BY region) AS region_avg
FROM
sales;
ここではSUM(total)とCOUNT(total)が各行ごとに何度も計算されてる。
修正方法: サブクエリやCTEを使ってクエリをシンプルにしよう:
WITH cte_region_totals AS (
SELECT
region,
SUM(total) AS region_total,
COUNT(total) AS region_count
FROM
sales
GROUP BY
region
)
SELECT
s.id,
s.total,
t.region_total,
t.region_total / t.region_count AS region_avg
FROM
sales s
JOIN
cte_region_totals t ON s.region = t.region;
ミスを避けるコツ
PARTITION BYとORDER BYを必ずチェック:ウィンドウの指定が正しいか毎回確認しよう。
インデックスを貼る:特にORDER BYやフィルタを使う場合は必須。
複数回の計算にはCTEを使う:重複計算を減らせるよ。
実行プランを確認する:EXPLAINやEXPLAIN ANALYZEでPostgreSQLがどうクエリを処理してるか見てみよう。
本番データでテストする:結果が期待通りか、ちゃんと目的が達成できてるか確認しよう。
GO TO FULL VERSION