CMD Guide
HomeDatabasesSQL Practice Problems

Game Play Analysis II

Problem

Table: Activity(player_id INT, device_id INT, event_date DATE, games_played INT). The pair (player_id, event_date) is the primary key. Each row is one login session: a player, on some device, on some date, playing some number of games. Report the device each player first logged in with.

Mechanism in one sentence

FIRST_VALUE(device_id) OVER (PARTITION BY player_id ORDER BY event_date) works because adding ORDER BY to a window silently installs the default frame RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW — so for every row the engine looks back from the partition's start up to that row, and the first element of that window is always the chronologically earliest device for the player.

Worked example

Concrete input — note player 1 logged in twice on the same device, and player 3 switched devices two years apart:

player_iddevice_idevent_dategames_played
122016-03-015
122016-05-026
232017-06-251
312016-03-020
342018-07-035

The query

SELECT DISTINCT
       player_id,
       FIRST_VALUE(device_id) OVER (
           PARTITION BY player_id
           ORDER BY event_date
       ) AS device_id
FROM Activity;

Step 1 — what the window function emits, row by row

The window function does not collapse rows; it appends a computed column to every input row. Within each player_id partition the rows are sorted by event_date, then for each row FIRST_VALUE reaches back to the start of the frame and returns the device from the earliest date. Because the frame's lower bound is fixed at the partition start, every row in a partition gets the same first device:

player_iddevice_idevent_dateframe seen (start..current)FIRST_VALUE → device_id
122016-03-01{2016-03-01}2
122016-05-02{2016-03-01, 2016-05-02}2
232017-06-25{2017-06-25}3
312016-03-02{2016-03-02}1
342018-07-03{2016-03-02, 2018-07-03}1

Five input rows in, five rows out — the result is still row-per-session, just with a repeated device_id column.

diagram
diagram

Step 2 — DISTINCT collapses the duplicates

After the window pass, every partition emits its first device repeated once per session row. We only project player_id and the computed device_id, then DISTINCT dedupes identical pairs. Player 1's two rows are both (1, 2) and collapse to one; player 3's two rows are both (3, 1) and collapse to one:

player_iddevice_id
12
23
31

Why not GROUP BY device_id?

The instinct SELECT player_id, device_id FROM Activity GROUP BY player_id (or with MIN(device_id)) is wrong: MIN(device_id) returns the smallest device number, not the device tied to the earliest date. For player 3 that would return device 1 by luck (1 < 4), but flip the input so the first login was on device 9 and a later login on device 2, and MIN answers 2 — the wrong device. You must order by event_date, never by the device id.

Three correct shapes — and their cost

All three return the right answer; they differ in how much work the engine does.

1. FIRST_VALUE + DISTINCT (the page's approach)

SELECT DISTINCT player_id,
       FIRST_VALUE(device_id) OVER (PARTITION BY player_id ORDER BY event_date) AS device_id
FROM Activity;

Computes the window over all N rows, then deduplicates. The window touches every session even though you keep one row per player — wasted work on heavy partitions, and DISTINCT adds a sort/hash pass on top.

2. ROW_NUMBER() = 1 (usually the cheapest, clearest)

SELECT player_id, device_id
FROM (
    SELECT player_id, device_id,
           ROW_NUMBER() OVER (PARTITION BY player_id ORDER BY event_date) AS rn
    FROM Activity
) t
WHERE rn = 1;

Same single window pass, but the WHERE rn = 1 filter throws away all but one row per player without a separate DISTINCT step. This is the canonical "first/last per group" pattern and the one to reach for by default.

3. Correlated subquery on MIN(event_date)

SELECT player_id, device_id
FROM Activity a
WHERE event_date = (
    SELECT MIN(event_date) FROM Activity b WHERE b.player_id = a.player_id
);

No window functions — portable to ancient engines (pre-8.0 MySQL). Reads clearly, but can re-scan the table per player without a good index on (player_id, event_date).

Pitfalls

Takeaways


Based on LeetCode 512 "Game Play Analysis II". Window-frame semantics per the ISO SQL standard and the PostgreSQL documentation on window functions and the default RANGE UNBOUNDED PRECEDING frame; the LAST_VALUE default-frame caveat and the ROW_NUMBER()=1 top-per-group pattern are standard practitioner knowledge (Markus Winand, Use The Index, Luke; PostgreSQL/MySQL 8.0 manuals). Re-authored and deepened for this guide — added the row-by-row frame trace, the diagram, the MIN(device_id) counter-example, and the three-approach cost comparison.

🤖 Don't fully get this? Learn it with Claude

Stuck on Game Play Analysis II? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Game Play Analysis II** (Databases) and want to truly understand it. Explain Game Play Analysis II from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Game Play Analysis II** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Game Play Analysis II** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Game Play Analysis II** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes