Skip to content
Reliable Data Engineering
Practice problem easy aggregationcount-distinctself-join
Solve it in the browser (SQL editor)

Daily Active Users and Stickiness

Difficulty: Easy · Topics: aggregation, count-distinct, self-join · Asked at: Meta, Snap, Spotify

Problem

From events(user_id, event_ts), compute for each day that has events:

Order by day.

Schema and sample data

CREATE TABLE events (user_id INTEGER, event_ts TEXT);
INSERT INTO events VALUES
(1,'2026-03-01 08:00'),(1,'2026-03-01 09:00'),(2,'2026-03-01 10:00'),
(1,'2026-03-02 08:00'),(3,'2026-03-02 11:00'),
(2,'2026-03-10 12:00'),
(4,'2026-03-29 07:00'),(1,'2026-03-29 08:00'),
(2,'2026-03-30 09:00');

Expected output

daydaumau_28dstickiness
2026-03-01221.0
2026-03-02230.67
2026-03-10130.33
2026-03-29240.5
2026-03-30130.33

Hints

Hint 1

COUNT(DISTINCT ...) OVER (...) is not supported in most engines. Use a self-join of days to activity in the trailing window instead.

Hint 2

Build a list of days first, then join user-days whose date is within [day-27, day].

Solution

WITH user_days AS (
  SELECT DISTINCT user_id, DATE(event_ts) AS d FROM events
), days AS (
  SELECT DISTINCT d FROM user_days
)
SELECT days.d AS day,
       COUNT(DISTINCT CASE WHEN ud.d = days.d THEN ud.user_id END) AS dau,
       COUNT(DISTINCT ud.user_id)                                  AS mau_28d,
       ROUND(1.0 * COUNT(DISTINCT CASE WHEN ud.d = days.d THEN ud.user_id END)
                 / COUNT(DISTINCT ud.user_id), 2)                   AS stickiness
FROM days
JOIN user_days ud
  ON ud.d BETWEEN DATE(days.d, '-27 days') AND days.d
GROUP BY days.d
ORDER BY days.d;

Explanation

Follow-up questions

How would you compute this for 2 billion events/day?

Daily job writes user_days (deduplicated) and a daily HLL sketch. MAU = union of the last 28 sketches. Exact MAU via user_days for finance-grade numbers, partitioned by date so only 28 partitions are scanned.

Why 28 days rather than a calendar month?

28 days contains exactly 4 of each weekday, so the metric has no weekday-mix seasonality and is comparable across months.

Dialect notes

Spark: approx_count_distinct(user_id) for approximate, size(collect_set(user_id) over w) works with a range window but is memory-heavy.