GROUP BY answers "how many runs did the team score?" by squashing many rows into one. Often you
want the total and the rows: each batter's runs next to the team total, each over's runs
next to the score so far. That's what window functions do. They compute across a set of rows
(the "window") and write the answer on every row, without squashing anything.
Any aggregate becomes a window function when you add OVER (...):
| Write | You get |
|---|---|
sum(runs) with GROUP BY team |
one row per team |
sum(runs) over (partition by team) |
every row, each with its team's total |
sum(runs) over (partition by team order by over_no) |
every row, each with the total so far |
Inside OVER: PARTITION BY splits the rows into groups (like GROUP BY, minus the squashing),
ORDER BY puts each group in order, and an optional frame (ROWS, RANGE, page 5.2) says which
rows around the current one count.
Window functions run late, after WHERE, GROUP BY and HAVING:
- FROM, JOINthe rows
- WHEREfilter rows (windows can't see rows removed here)
- GROUP BYgroups
- HAVINGfilter groups
- window functionscomputed over what's left, one value per row
- SELECToutput columns
- ORDER BYsort
That order is why you can't put a window function in WHERE (it hasn't been computed yet), and
why a window function can sit on top of a GROUP BY: sum(sum(runs)) over () is legal, and
means "the total of the group totals".
The test case
In the first innings of the 2026 final (match 32), show each Chennai batter's runs, the team's runs off the bat, and each batter's share as a percentage (one decimal), in batting order.
Expected result
| PLAYER_NAME | RUNS | TEAM_RUNS | PCT |
|---|---|---|---|
| Karthik Raman | 29 | 152 | 19.1 |
| Siddharth Iyer | 16 | 152 | 10.5 |
| Vignesh Babu | 36 | 152 | 23.7 |
| Pranav Nair | 0 | 152 | 0 |
| Harish Kumar | 0 | 152 | 0 |
| Arvind Selvam | 4 | 152 | 2.6 |
| Rahul Menon | 28 | 152 | 18.4 |
| Gokul Prakash | 16 | 152 | 10.5 |
| Senthil Murugan | 12 | 152 | 7.9 |
| Naveen Reddy | 11 | 152 | 7.2 |
| Ashwath Krishnan | 0 | 152 | 0 |
Vignesh Babu top-scored with 36 at number 3; three batters made ducks. In a final. Ouch.
Way 1: SUM() OVER ()
with by_batter as (
select batter_id, min(seq_no) as first_ball, sum(runs_batter) as runs
from cr_balls_v
where match_id = 32 and innings_no = 1
group by batter_id
)
select p.player_name,
b.runs,
sum(b.runs) over () as team_runs,
round(100 * b.runs / sum(b.runs) over (), 1) as pct
from by_batter b
join cr_players p on p.player_id = b.batter_id
order by b.first_ball;
OVER () with nothing inside is a window of all the rows, so sum(b.runs) over () is the grand
total, repeated on each row.
Way 2: RATIO_TO_REPORT
with by_batter as (
select batter_id, min(seq_no) as first_ball, sum(runs_batter) as runs
from cr_balls_v
where match_id = 32 and innings_no = 1
group by batter_id
)
select p.player_name,
b.runs,
sum(b.runs) over () as team_runs,
round(100 * ratio_to_report(b.runs) over (), 1) as pct
from by_batter b
join cr_players p on p.player_id = b.batter_id
order by b.first_ball;
RATIO_TO_REPORT(x) OVER (...) is x / sum(x) over (...) in one function. An Oracle extra that
saves typing whenever you need shares.
Way 3: join to a total (no window function)
with by_batter as (
select batter_id, min(seq_no) as first_ball, sum(runs_batter) as runs
from cr_balls_v
where match_id = 32 and innings_no = 1
group by batter_id
),
total as (
select sum(runs) as team_runs from by_batter
)
select p.player_name, b.runs, t.team_runs,
round(100 * b.runs / t.team_runs, 1) as pct
from by_batter b
cross join total t
join cr_players p on p.player_id = b.batter_id
order by b.first_ball;
How it was done before window functions: compute the total separately and join it back. Fine for one total; with totals per team, per season and per phase you'd need a join for each, while window functions just add a column each.
19c and 26ai
The same on both. Window functions have been in Oracle since 8i (1999), long before most databases had them.
Gotchas
WHEREruns first. Filter batters withWHEREand the total shrinks too. To keep the full total but show only some rows, filter in an outer query (or withQUALIFYon 26ai, page 12.2).- Integer division isn't a thing in Oracle, so
100 * 29 / 152is 19.07..., not 0. (In some other databases it would be.) OVER ()means everything, which is easy to forget when you meantOVER (PARTITION BY team).