第一部 DB 基礎編 · 補足トピック · 約 20 分
補足: 行を潰さずに集計する
集計しながら個別の行を残せるウィンドウ関数。ROW_NUMBER、累計値、前後の行との差分の算出方法を学びます(スキップしても第二部に進めます)。
※ 本章は補足トピックです。必要に応じてスキップして第 7 章へ進んでも、第二部の学習に影響はありません。
この章で答える問い
- 行数を減らさずに集計値を付加するにはどうするか
- グループごとの「最新の1件」を取り出すにはどう書くか
- 累計や前後の行との差分を計算するにはどうするか
ウィンドウ関数を体験: 個別の行を維持したまま集計する
各明細の横に、商品ごとの平均単価
期待: 200 行(減っていない)。同じ product_id の行は同じ平均
全 200 行が 1 行も減らずにそのまま出力されています。AVG(...) OVER (PARTITION BY product_id) は、「product_id が同じグループ(窓)の中で平均値を計算し、その結果を行を潰さずに各行の新しい列として付加する」という処理を行います。 このように行の枠組み(ウィンドウ)を定義して計算を行うため、ウィンドウ関数(分析関数)と呼ばれます。 GROUP BY が行をグループごとに集約して 1 行にまとめるのに対し、ウィンドウ関数は元の明細行を維持したまま集計値を横に添えるのが特徴です。
順位付け: ROW_NUMBER
顧客ごとに、注文の新しい順の番号
期待: 50 行。各顧客の中で 1, 2, 3 …
顧客ごとの最新の注文だけ(チケット完了)
期待: 8 行(注文のある顧客の数)
「グループごとの最新 1 件」や「各カテゴリの売上上位 3 件」を取得する処理は、実務でウィンドウ関数と WITH 句を組み合わせる定番のパターンです。ROW_NUMBER() は値が重複していても必ず一意の連番(1, 2, 3…)を振ります。 同順位を同列として扱いたい場合は RANK()(1, 1, 3…)や DENSE_RANK()(1, 1, 2…)を使用します。
累計計算と前後の行との差分比較
日ごとの注文数と累積
期待: 日付順に累積が増えていく
前日との差(LAG)
期待: 最初の行の前日は NULL
7 日移動平均
期待: ROWS BETWEEN で窓の幅を指定
ウィンドウ関数は行を潰さずに、窓ごとの集計値・順位・前後の行の値を各行に付ける。
確認
同じ列で束ねても、GROUP BY と OVER (PARTITION BY) で結果の行数が違うのはなぜか。
GROUP BY は窓ごとに 1 行へ潰すが、OVER は窓ごとに計算した値を窓の中の各行にそのまま付けるから。だから OVER では元の行が残る。
グループごとの最新の 1 件だけを取り出すには、どう組み立てるか。
窓の中を並べて番号を振り、その結果に WITH で名前を付けてから、番号が先頭の行だけを残す。番号を振る段階では行が減らないので、絞るのは後段になる。
順位を付けるとき、ROW_NUMBER と RANK と DENSE_RANK は何が違うか。
ROW_NUMBER は同点でも別々の番号を振り、RANK は同点を同じ順位にして次の順位を飛ばし、DENSE_RANK は飛ばさない。グループごとに 1 行だけ欲しいときは、必ず 1 つに決まる ROW_NUMBER を使う。