Window functions
over gives a function a window, which is what turns an aggregate into a
running one and the only thing row_number and its kind can be used with.
The window is built by chaining, as Arel's own is:
Author.select { avg(:age).over.partition(:country).as(:country_average) }
# AVG("age") OVER (PARTITION BY "country") AS country_average
Author.select { row_number.over.partition(:country).order(:age.desc).as(:rank) }
# ROW_NUMBER() OVER (PARTITION BY "country" ORDER BY "age" DESC) AS rank
Author.select { count(:*).over.as(:total) } # COUNT(*) OVER () — every row
row_number, rank, dense_rank, percent_rank, cume_dist, ntile,
lag, lead, first_value, last_value and nth_value are the functions
that say nothing without a window; each raises ArgumentError if over never
arrives, rather than reaching the database as an error there. Every adapter
that has window functions at all spells them the same way, so unlike the
scalar functions there is nothing here to translate.
A frame is a range of rows counted from the current one — negative before it, positive after, 0 the row itself, and an open end for unbounded:
Post.select { sum(:likes).over.order(:created_at).rows(..0).as(:running) }
# SUM("likes") OVER (ORDER BY "created_at" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
Post.select { avg(:likes).over.order(:created_at).rows(-1..1).as(:smoothed) }
# ... ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
Post.select { sum(:likes).over.order(:created_at).rows(0..).as(:remaining) }
# ... ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
range says RANGE where rows says ROWS, and a window has one frame or
none. Named windows — WINDOW w AS (...) — have no clause in Active Record to
live in, so they are not here.