runtime
45 / 88

lesson 45 Window functions · advanced

Top N per group & ties

Find the leading records within each group and handle ties.

the idea

Aggregate watched seconds per video, then rank videos within creator. ROW_NUMBER gives an exact N with a tie-breaker; DENSE_RANK includes ties and may return more than N. Make the tie policy explicit.

tables in this lesson

The challenges use this data. Each column shows its type underneath; → marks a link to another table.

videos4 columns · 6 rows
idinteger · keycreator_idintegertitletextduration_secondsinteger
110SQL in ten minutes600
210Understanding joins900
311A day outdoors300
412Window functions720
512Build a project1200

showing 5 of 6 rows

watch_events5 columns · 26 rows
idinteger · keyviewer_idintegervideo_idintegerwatched_attextwatch_secondsinteger
1112025-01-01 10:00:00300
2122025-01-01 11:00:00450
3212025-01-02 12:00:00600
4332025-01-02 13:00:00120
5142025-01-02 14:00:00700

showing 5 of 26 rows

example

The idea in one query. Press Run to see what it returns; running examples never affects your progress.

example.sql
WITH totals AS(
  SELECT
    v.creator_id,
    v.id AS video_id,
    SUM(w.watch_seconds) AS watch_seconds
  FROM videos v
  JOIN watch_events w ON w.video_id=v.id
  GROUP BY v.id,v.creator_id
), ranked AS(
  SELECT
    *,
    ROW_NUMBER() OVER(PARTITION BY creator_id ORDER BY watch_seconds DESC,video_id) AS rn
  FROM totals
)
SELECT creator_id,video_id,watch_seconds
FROM ranked
WHERE rn<=2;

Watch for: Adding video_id to a DENSE_RANK ordering eliminates watch-time ties.

build it step by step

Each step changes one thing. Run them in order and watch the result change.

  1. Rank videos inside each creator. Top-N per group needs a partition. Aggregate watch time per video, then number videos within each creator.

    step-1.sql
    SELECT
      v.creator_id,
      v.id AS video_id,
      SUM(w.watch_seconds) AS watch_seconds
    FROM videos v
    JOIN watch_events w ON w.video_id = v.id
    GROUP BY v.creator_id, v.id
    ORDER BY v.creator_id, watch_seconds DESC;
  2. Keep the top rows per group. ROW_NUMBER or RANK inside the creator partition selects the top videos. Ties need an explicit rule.

    step-2.sql
    WITH totals AS(
      SELECT
        v.creator_id,
        v.id AS video_id,
        SUM(w.watch_seconds) AS watch_seconds
      FROM videos v
      JOIN watch_events w ON w.video_id=v.id
      GROUP BY v.id,v.creator_id
    ), ranked AS(
      SELECT
        *,
        ROW_NUMBER() OVER(PARTITION BY creator_id ORDER BY watch_seconds DESC,video_id) AS rn
      FROM totals
    )
    SELECT creator_id,video_id,watch_seconds
    FROM ranked
    WHERE rn<=2;

challenge 1

Return creator_id, video_id and watch_seconds for the top 2 watched videos per creator, excluding videos with no events. Rank by total seconds descending, break ties by smallest video_id.

Return creator_idvideo_idwatch_secondsany row order

challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.