lesson 45 Window functions · advanced
Top N per group & ties
Find the leading records within each group and handle ties.
the idea
Top-N per group = rank within each group, then filter. DENSE_RANK keeps every player at the top N distinct values, ties included; ROW_NUMBER keeps exactly N rows per group and needs a tie-breaker to decide who is cut.
Dataset: Sports
The tables the challenges use, with their first 6 rows. Under each column name: the kind of value it holds, and which table it links to.
games13 columns · 23,168 rows
| game_idtext · unique id | seasonnumber · links to seasons | game_datedate | game_numbernumber | game_typetext | venue_idtext · links to venues | home_team_idtext · links to teams | away_team_idtext · links to teams | home_scorenumber | away_scorenumber | day_nighttext | attendancenumber | duration_minutesnumber |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ANA201604040 | 2016 | 2016-04-04 | 0 | regular | ANA01 | ANA | CHN | 0 | 9 | night | 44020 | 188 |
| ANA201604050 | 2016 | 2016-04-05 | 0 | regular | ANA01 | ANA | CHN | 1 | 6 | night | 37042 | 154 |
| ANA201604070 | 2016 | 2016-04-07 | 0 | regular | ANA01 | ANA | TEX | 4 | 3 | night | 39089 | 191 |
| ANA201604080 | 2016 | 2016-04-08 | 0 | regular | ANA01 | ANA | TEX | 3 | 7 | night | 35207 | 210 |
| ANA201604090 | 2016 | 2016-04-09 | 0 | regular | ANA01 | ANA | TEX | 1 | 4 | night | 38106 | 173 |
| ANA201604100 | 2016 | 2016-04-10 | 0 | regular | ANA01 | ANA | TEX | 3 | 1 | day | 35097 | 163 |
- game_idtext · unique id
- ANA201604040
- seasonnumber · links to seasons
- 2016
- game_datedate
- 2016-04-04
- game_numbernumber
- 0
- game_typetext
- regular
- venue_idtext · links to venues
- ANA01
- home_team_idtext · links to teams
- ANA
- away_team_idtext · links to teams
- CHN
- home_scorenumber
- 0
- away_scorenumber
- 9
- day_nighttext
- night
- attendancenumber
- 44020
- duration_minutesnumber
- 188
- game_idtext · unique id
- ANA201604050
- seasonnumber · links to seasons
- 2016
- game_datedate
- 2016-04-05
- game_numbernumber
- 0
- game_typetext
- regular
- venue_idtext · links to venues
- ANA01
- home_team_idtext · links to teams
- ANA
- away_team_idtext · links to teams
- CHN
- home_scorenumber
- 1
- away_scorenumber
- 6
- day_nighttext
- night
- attendancenumber
- 37042
- duration_minutesnumber
- 154
- game_idtext · unique id
- ANA201604070
- seasonnumber · links to seasons
- 2016
- game_datedate
- 2016-04-07
- game_numbernumber
- 0
- game_typetext
- regular
- venue_idtext · links to venues
- ANA01
- home_team_idtext · links to teams
- ANA
- away_team_idtext · links to teams
- TEX
- home_scorenumber
- 4
- away_scorenumber
- 3
- day_nighttext
- night
- attendancenumber
- 39089
- duration_minutesnumber
- 191
- game_idtext · unique id
- ANA201604080
- seasonnumber · links to seasons
- 2016
- game_datedate
- 2016-04-08
- game_numbernumber
- 0
- game_typetext
- regular
- venue_idtext · links to venues
- ANA01
- home_team_idtext · links to teams
- ANA
- away_team_idtext · links to teams
- TEX
- home_scorenumber
- 3
- away_scorenumber
- 7
- day_nighttext
- night
- attendancenumber
- 35207
- duration_minutesnumber
- 210
- game_idtext · unique id
- ANA201604090
- seasonnumber · links to seasons
- 2016
- game_datedate
- 2016-04-09
- game_numbernumber
- 0
- game_typetext
- regular
- venue_idtext · links to venues
- ANA01
- home_team_idtext · links to teams
- ANA
- away_team_idtext · links to teams
- TEX
- home_scorenumber
- 1
- away_scorenumber
- 4
- day_nighttext
- night
- attendancenumber
- 38106
- duration_minutesnumber
- 173
- game_idtext · unique id
- ANA201604100
- seasonnumber · links to seasons
- 2016
- game_datedate
- 2016-04-10
- game_numbernumber
- 0
- game_typetext
- regular
- venue_idtext · links to venues
- ANA01
- home_team_idtext · links to teams
- ANA
- away_team_idtext · links to teams
- TEX
- home_scorenumber
- 3
- away_scorenumber
- 1
- day_nighttext
- day
- attendancenumber
- 35097
- duration_minutesnumber
- 163
first 6 of 23,168 rows · your queries use all of them
player_game_stats17 columns · 73,029 rows
| game_idtext · links to games | player_idtext · links to players | team_idtext · links to teams | batting_ordernumber | startednumber | plate_appearancesnumber | at_batsnumber | runsnumber | hitsnumber | doublesnumber | triplesnumber | home_runsnumber | rbinumber | walksnumber | strikeoutsnumber | stolen_basesnumber | hit_by_pitchnumber |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ANA202504040 | adelj001 | ANA | 8 | 1 | 5 | 5 | 0 | 2 | 1 | 0 | 0 | 3 | 0 | 2 | 0 | 0 |
| ANA202504040 | andet001 | ANA | 4 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ANA202504040 | ariag002 | CLE | 8 | 1 | 4 | 2 | 1 | 1 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 |
| ANA202504040 | clase001 | CLE | NULL | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ANA202504040 | danac001 | ANA | NULL | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ANA202504040 | darnt001 | ANA | 9 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
- game_idtext · links to games
- ANA202504040
- player_idtext · links to players
- adelj001
- team_idtext · links to teams
- ANA
- batting_ordernumber
- 8
- startednumber
- 1
- plate_appearancesnumber
- 5
- at_batsnumber
- 5
- runsnumber
- 0
- hitsnumber
- 2
- doublesnumber
- 1
- triplesnumber
- 0
- home_runsnumber
- 0
- rbinumber
- 3
- walksnumber
- 0
- strikeoutsnumber
- 2
- stolen_basesnumber
- 0
- hit_by_pitchnumber
- 0
- game_idtext · links to games
- ANA202504040
- player_idtext · links to players
- andet001
- team_idtext · links to teams
- ANA
- batting_ordernumber
- 4
- startednumber
- 0
- plate_appearancesnumber
- 1
- at_batsnumber
- 1
- runsnumber
- 0
- hitsnumber
- 0
- doublesnumber
- 0
- triplesnumber
- 0
- home_runsnumber
- 0
- rbinumber
- 0
- walksnumber
- 0
- strikeoutsnumber
- 0
- stolen_basesnumber
- 0
- hit_by_pitchnumber
- 0
- game_idtext · links to games
- ANA202504040
- player_idtext · links to players
- ariag002
- team_idtext · links to teams
- CLE
- batting_ordernumber
- 8
- startednumber
- 1
- plate_appearancesnumber
- 4
- at_batsnumber
- 2
- runsnumber
- 1
- hitsnumber
- 1
- doublesnumber
- 0
- triplesnumber
- 0
- home_runsnumber
- 0
- rbinumber
- 0
- walksnumber
- 2
- strikeoutsnumber
- 0
- stolen_basesnumber
- 0
- hit_by_pitchnumber
- 0
- game_idtext · links to games
- ANA202504040
- player_idtext · links to players
- clase001
- team_idtext · links to teams
- CLE
- batting_ordernumber
- NULL
- startednumber
- 0
- plate_appearancesnumber
- 0
- at_batsnumber
- 0
- runsnumber
- 0
- hitsnumber
- 0
- doublesnumber
- 0
- triplesnumber
- 0
- home_runsnumber
- 0
- rbinumber
- 0
- walksnumber
- 0
- strikeoutsnumber
- 0
- stolen_basesnumber
- 0
- hit_by_pitchnumber
- 0
- game_idtext · links to games
- ANA202504040
- player_idtext · links to players
- danac001
- team_idtext · links to teams
- ANA
- batting_ordernumber
- NULL
- startednumber
- 0
- plate_appearancesnumber
- 0
- at_batsnumber
- 0
- runsnumber
- 0
- hitsnumber
- 0
- doublesnumber
- 0
- triplesnumber
- 0
- home_runsnumber
- 0
- rbinumber
- 0
- walksnumber
- 0
- strikeoutsnumber
- 0
- stolen_basesnumber
- 0
- hit_by_pitchnumber
- 0
- game_idtext · links to games
- ANA202504040
- player_idtext · links to players
- darnt001
- team_idtext · links to teams
- ANA
- batting_ordernumber
- 9
- startednumber
- 0
- plate_appearancesnumber
- 1
- at_batsnumber
- 1
- runsnumber
- 0
- hitsnumber
- 0
- doublesnumber
- 0
- triplesnumber
- 0
- home_runsnumber
- 0
- rbinumber
- 0
- walksnumber
- 0
- strikeoutsnumber
- 0
- stolen_basesnumber
- 0
- hit_by_pitchnumber
- 0
first 6 of 73,029 rows · your queries use all of them
worked example
Run these steps in order to see the idea before you write your own query. The step marked “the pattern” is what the challenges ask for. Running the example does not count toward progress.
Pick one hits leader per team. Run this. After summing hits by player, ROW_NUMBER() = 1 keeps a single top hitter per team, breaking ties with the smaller player_id.
step-1.sqlSELECT team_id, player_id, hits FROM ( SELECT s.team_id, s.player_id, SUM(s.hits) AS hits, ROW_NUMBER() OVER (PARTITION BY s.team_id ORDER BY SUM(s.hits) DESC, s.player_id) AS rn FROM player_game_stats s JOIN games g ON g.game_id = s.game_id WHERE g.game_type = 'regular' GROUP BY s.team_id, s.player_id ) WHERE rn = 1;
Find tied home-run totals. Run this. Groups with more than one player show why DENSE_RANK keeps every tied name when you ask for “top two totals.”
step-2.sqlSELECT team_id, home_runs, COUNT(*) AS players FROM ( SELECT s.team_id, s.player_id, SUM(s.home_runs) AS home_runs FROM player_game_stats s JOIN games g ON g.game_id = s.game_id WHERE g.game_type = 'regular' GROUP BY s.team_id, s.player_id ) WHERE home_runs > 15 GROUP BY team_id, home_runs HAVING COUNT(*) > 1;
Put it together. The lesson's pattern in one query.the pattern
example.sqlSELECT team_id, player_id, hits FROM ( SELECT s.team_id, s.player_id, SUM(s.hits) AS hits, ROW_NUMBER() OVER (PARTITION BY s.team_id ORDER BY SUM(s.hits) DESC, s.player_id) AS rn FROM player_game_stats s JOIN games g ON g.game_id = s.game_id WHERE g.game_type = 'regular' GROUP BY s.team_id, s.player_id ) WHERE rn = 1;
common mistake
LIMIT applies to the whole result, not to each team.