SQL Lab
第一部 DB 基礎編 · 補足トピック · 約 20

補足: 行を潰さずに集計する

ウィンドウ関数。ROW_NUMBER、累積和、前の行との差。飛ばしても第二部に影響しない。

補足トピック。飛ばして第 7 章へ進んでも、第二部には影響しない。

この章で答える問い
  1. 行を減らさずに集計値を付けるには
  2. グループごとの「1 番目」を取るには
  3. 累積や前の行との差を出すには

実物: 行が減っていない集計

各明細の横に、商品ごとの平均単価
期待: 200 行(減っていない)。同じ product_id の行は同じ平均

200 行のままだ。AVG(...) OVER (PARTITION BY product_id) は「product_id ごとの窓の中で平均を計算し、その値を各行に付ける」。 窓(ウィンドウ)を作って集計するのでウィンドウ関数と呼ぶ。GROUP BY が束ねて潰すのに対し、こちらは束ねて付ける。 同じ 4 行に GROUP BY と OVER を当てて、行数がどうなるかを並べる。

order_items(4 行)idproduct_idunit_price6425057425017542507629620点線 = 窓(PARTITION BY product_id)束ねて潰す(4 → 2 行)GROUP BY product_idproduct_idAVG(unit_price)425029620束ねて付ける(4 → 4 行)AVG(unit_price) OVER (PARTITION BY product_id)idproduct_idunit_price商品平均6425025057425025017542502507629620620
GROUP BY は窓ごとに 1 行へ潰す。OVER (PARTITION BY) は窓ごとの値を、窓の中の各行にそのまま付ける。

順位: ROW_NUMBER

顧客ごとに、注文の新しい順の番号
期待: 50 行。各顧客の中で 1, 2, 3 …
顧客ごとの最新の注文だけ(チケット完了)
期待: 8 行(注文のある顧客の数)

「グループごとの上位 N 件」は、ウィンドウ関数 + 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 を使う。