Where the Minutes Go

A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
hard
Seniority
L5
Asked in
Fidelity

Problem

Our analytics warehouse stores one row per page view, tagged with the visitor's device and how long they stayed on the page. Total the time on page for each device type, and next to each device show the cumulative share of overall time accounted for when devices are counted up from the biggest total down through that one.

Attention piles up device by device. Find where it pools.

Tables

page_views

Columns: view_id INTEGER, page_url TEXT, user_id INTEGER, referrer TEXT, dur_ms INTEGER, device TEXT, viewed_at TEXT

page_views: 10 of 200 rows
view_idpage_urluser_idreferrerdur_msdeviceviewed_at
5053/products1555amazon.com637desktop2026-09-02 01:09:00
5106/checkout1652twitter.com774tablet2026-09-03 02:18:00
5159/profile1749github.com911Mobile2026-09-04 03:27:00
5212/search1846NULL1048DESKTOP2026-09-05 04:36:00
5265/about1943reddit.com1185mobile2026-09-06 05:45:00
5318/blog/intro1555NULLNULLdesktop2026-09-07 06:54:00
5371/pricing1652bing.com1459tablet2026-09-08 07:03:00
5424/docs/api1749news.ycombinator.com1596Mobile2026-09-09 08:12:00
5477/settings1846NULL1733DESKTOP2026-09-10 09:21:00
5530/Home1943bing.com1870mobile2026-09-11 10:30:00

Expected output

First 3 of 3 rows
devicetotal_dwell_msrunning_pct
mobile50966440.97
desktop48071679.61
tablet253736100

Worked solution and explanation

What they are really asking

This is a percent-of-total leaderboard wearing a device-analytics costume, and it hides a normalization landmine. The real skill: compute each device's share of total time on page, then accumulate those shares into a running coverage figure, all against a grand total you derive in the SAME pass. Anyone can sum time per device. The separator is two things: folding the case-scrambled device strings BEFORE you group, and expressing the grand total and the running coverage so the numerator and denominator always agree. Miss the fold and the raw column hands you DESKTOP and desktop as different devices; your three-row leaderboard becomes five rows and no cumulative share ever climbs to 100.


The landmine: normalize before you aggregate

Building it up

Step 1: Collapse case, then total the time per device

In a CTE, GROUP BY LOWER(device) and SUM(dur_ms). SUM skips the NULL dwell values for free, so views with no recorded time simply contribute nothing rather than breaking the total. You now have exactly three rows: mobile, desktop, tablet, each with its total_dwell_ms.

Step 2: Get the grand total in the same expression

SUM(total_dwell_ms) OVER () with an empty window returns the total across all three device rows on every row, so you can divide each device's time by the whole without a separate total subquery to keep in sync. Because that denominator is read off the same folded rows as the numerator, the two can never drift apart.

Step 3: Accumulate the running coverage

SUM(total_dwell_ms) OVER (ORDER BY total_dwell_ms DESC) is a prefix sum: on the biggest device it is that device's time, on the second it is the top two combined, and so on. A plain GROUP BY cannot reach across rows to build this; the ordered window is what expresses it. Divide the prefix sum by the grand total, times 100, and round to two places to get the cumulative share the leaderboard has covered through each row.

The solution

One CTE, two windows, one consistent pass
WITH per_device AS (
  SELECT LOWER(device) AS device, SUM(dur_ms) AS total_dwell_ms
  FROM page_views
  GROUP BY LOWER(device)
)
SELECT
  device,
  total_dwell_ms,
  ROUND(100.0 * SUM(total_dwell_ms) OVER (ORDER BY total_dwell_ms DESC)
        / SUM(total_dwell_ms) OVER (), 2) AS running_pct
FROM per_device

LOWER folds the device case; the two SUM windows give grand total and running coverage off the same folded rows, so numerator and denominator always agree.

Self-join plus a total subquery
To get the running coverage you self-join the grouped set to itself (each device against every device ordered at or above it), and you pull the denominator from a separate SUM subquery. Two moving parts to keep aligned, and it is easy to compute that denominator over the raw unfolded device rows by mistake, so the numerator and denominator disagree and no row lands on 100.
Two window aggregates
SUM(total_dwell_ms) OVER (ORDER BY total_dwell_ms DESC) gives the prefix sum and SUM(total_dwell_ms) OVER () gives the grand total, both off the same already-folded CTE rows. One expression, no join to align, and the denominator is guaranteed consistent with the numerator because both come from the same rows.

Common follow-up questions