cswork / libraryOpen practice workspace

Coding questions · Flatiron Health

Write SQL window functions for streaks

This question evaluates proficiency with SQL window functions, date arithmetic, gap-and-island (streak) detection, rolling aggregates, and event-order sequencing to derive DAU, revenue metrics, and inactivity gaps.

Assume today is 2025-09-01. Using standard SQL (e.g., PostgreSQL), answer the following using window functions only (no procedural loops, no correlated subqueries). Schema and small samples:

Schema

  • users(user_id INT, signup_date DATE)
  • events(user_id INT, event_date DATE, event_name TEXT) -- one row per user per day per event
  • orders(order_id INT, user_id INT, order_date DATE, amount DECIMAL(8,2))

Sample rows

users
user_id | signup_date
1 | 2025-08-20
2 | 2025-08-22
3 | 2025-08-25
events
user_id | event_date | event_name
1 | 2025-08-29 | login
1 | 2025-08-30 | login
1 | 2025-09-01 | login
2 | 2025-08-26 | login
2 | 2025-08-28 | login
2 | 2025-08-29 | login
2 | 2025-09-01 | login
3 | 2025-08-25 | login
3 | 2025-08-26 | login
3 | 2025-08-27 | login
3 | 2025-09-01 | login
orders
order_id | user_id | order_date | amount
10 | 1 | 2025-08-30 | 20.00
11 | 2 | 2025-08-27 | 15.00
12 | 2 | 2025-09-01 | 30.00
13 | 3 | 2025-08-26 | 12.00

Tasks A) Return, for each user, the first 3-day consecutive activity streak whose streak-end falls within 2025-08-26 to 2025-09-01 inclusive. Activity is defined as having at least one events row (any event_name) on a date. Output: user_id, streak_start_date, streak_end_date. Use only window functions and date arithmetic (no self-joins on dates tables). B) For each calendar day d in 2025-08-26..2025-09-01, compute: (i) DAU = distinct users with any event on d, (ii) revenue_d = sum of orders.amount with order_date = d, and (iii) rolling_7d_DAU_avg = 7-day trailing average of DAU ending on d. Also compute revenue_per_active_user = revenue_d / DAU with 4-decimal precision, treating division-by-zero as NULL. Return day, DAU, revenue_d, rolling_7d_DAU_avg, revenue_per_active_user. C) Flag users who placed any order on date t and then had a strict 10-day inactivity gap (no events) immediately after t, followed by any event on a later date u. Return user_id, last_order_date = t, gap_days, first_post_gap_event_date = u. Only the earliest such gap per user.

First 3-Day Consecutive Activity Streak per User

Using the tables defined below, write a standard SQL query (using window functions and date arithmetic only; no procedural loops and no correlated subqueries) to return, for each user, the first 3-day consecutive activity streak whose streak_end falls between 2025-08-26 and 2025-09-01 inclusive. Activity is defined as having at least one row in the events table (any event_name) on a given date. A 3-day streak means the user is active on three consecutive calendar days. The output should include only users who have at least one such 3-day streak and must have the columns: user_id, streak_start_date, streak_end_date.

Table users

columntypeconstraints
user_idINTPRIMARY KEY
signup_dateDATENOT NULL

Sample data:

signup_dateuser_id
2025-08-201
2025-08-222
2025-08-253
2025-08-184

Table events

columntypeconstraints
user_idINTNOT NULL
event_dateDATENOT NULL
event_nameVARCHAR(50)NOT NULL

Sample data:

event_dateevent_nameuser_id
2025-08-29login1
2025-08-30login1
2025-09-01login1
2025-08-26login2
2025-08-28login2
2025-08-29login2
2025-09-01login2
2025-08-25login3
2025-08-26login3
2025-08-27login3
2025-09-01login3
2025-08-20login4
2025-09-10login4

Table orders

columntypeconstraints
order_idINTPRIMARY KEY
user_idINTNOT NULL
order_dateDATENOT NULL
amountDECIMAL(8,2)NOT NULL

Sample data:

amountorder_dateorder_iduser_id
202025-08-30101
152025-08-27112
302025-09-01122
122025-08-26133
502025-08-20144

Expected output

One row per user who has at least one 3-day streak of consecutive daily activity, where the streak_end_date is between 2025-08-26 and 2025-09-01 inclusive. For the sample data, only user 3 has such a streak (2025-08-25, 2025-08-26, 2025-08-27).

streak_end_datestreak_start_dateuser_id
2025-08-272025-08-253

Hints

  1. First collapse to one row per (user_id, event_date), then order each user’s dates and assign ROW_NUMBER().
  2. Use the trick of grouping by (event_date - row_number * 1 day) to find runs of consecutive dates, filter runs with length >= 3, then pick the earliest qualifying streak per user.

Daily Metrics and 7-Day Rolling DAU Average

Using the same tables, write a standard SQL query (using window functions and date arithmetic; no procedural loops and no correlated subqueries) to compute, for each calendar day d from 2025-08-26 through 2025-09-01 inclusive: (i) DAU = the number of distinct users with any event on d; (ii) revenue_d = the sum of orders.amount where order_date = d; (iii) rolling_7d_DAU_avg = the 7-day trailing average of DAU ending on d, computed over the sequence of calendar days (for the first few days, average over all available preceding days in the range); and (iv) revenue_per_active_user = revenue_d / DAU with 4-decimal precision, treating division-by-zero as NULL. The output columns should be: day, DAU, revenue_d, rolling_7d_DAU_avg, revenue_per_active_user, and must include all days in the date range even if DAU or revenue_d are zero.

Table users

columntypeconstraints
user_idINTPRIMARY KEY
signup_dateDATENOT NULL

Sample data:

signup_dateuser_id
2025-08-201
2025-08-222
2025-08-253
2025-08-184

Table events

columntypeconstraints
user_idINTNOT NULL
event_dateDATENOT NULL
event_nameVARCHAR(50)NOT NULL

Sample data:

event_dateevent_nameuser_id
2025-08-29login1
2025-08-30login1
2025-09-01login1
2025-08-26login2
2025-08-28login2
2025-08-29login2
2025-09-01login2
2025-08-25login3
2025-08-26login3
2025-08-27login3
2025-09-01login3
2025-08-20login4
2025-09-10login4

Table orders

columntypeconstraints
order_idINTPRIMARY KEY
user_idINTNOT NULL
order_dateDATENOT NULL
amountDECIMAL(8,2)NOT NULL

Sample data:

amountorder_dateorder_iduser_id
202025-08-30101
152025-08-27112
302025-09-01122
122025-08-26133
502025-08-20144

Expected output

One row per calendar day between 2025-08-26 and 2025-09-01 inclusive, showing DAU, total revenue for that day, the trailing 7-day average of DAU over the date sequence, and revenue_per_active_user as revenue_d / DAU (NULL when DAU = 0).

DAUdayrevenue_drevenue_per_active_userrolling_7d_DAU_avg
22025-08-261262
12025-08-2715151.5
12025-08-28001.3333
22025-08-29001.5
12025-08-3020201.4
02025-08-310NULL1.1667
32025-09-0130101.4286

Hints

  1. Construct a calendar of dates for 2025-08-26 through 2025-09-01 and LEFT JOIN to events and orders to compute DAU and revenue per day.
  2. Use a window AVG over ROWS BETWEEN 6 PRECEDING AND CURRENT ROW for the rolling 7-day DAU average, and use NULLIF in the denominator to avoid division-by-zero when computing revenue_per_active_user.

Detect Users with 10-Day Inactivity Gaps After Orders

Using the same tables, write a standard SQL query (using window functions and date arithmetic; no procedural loops and no correlated subqueries) to flag users who placed an order on some date t and then had a strict 10-day inactivity gap immediately after t (no events on any of the days t+1, t+2, ..., t+10), followed by at least one later event on a date u > t+10. For each such user, return only the earliest qualifying gap. The output columns should be: user_id, last_order_date = t, gap_days, first_post_gap_event_date = u, where gap_days is the number of full days between t and u, excluding both endpoints (equivalent to DATEDIFF(u, t) - 1 in days).

Table users

columntypeconstraints
user_idINTPRIMARY KEY
signup_dateDATENOT NULL

Sample data:

signup_dateuser_id
2025-08-201
2025-08-222
2025-08-253
2025-08-184

Table events

columntypeconstraints
user_idINTNOT NULL
event_dateDATENOT NULL
event_nameVARCHAR(50)NOT NULL

Sample data:

event_dateevent_nameuser_id
2025-08-29login1
2025-08-30login1
2025-09-01login1
2025-08-26login2
2025-08-28login2
2025-08-29login2
2025-09-01login2
2025-08-25login3
2025-08-26login3
2025-08-27login3
2025-09-01login3
2025-08-20login4
2025-09-10login4

Table orders

columntypeconstraints
order_idINTPRIMARY KEY
user_idINTNOT NULL
order_dateDATENOT NULL
amountDECIMAL(8,2)NOT NULL

Sample data:

amountorder_dateorder_iduser_id
202025-08-30101
152025-08-27112
302025-09-01122
122025-08-26133
502025-08-20144

Expected output

One row per user who has at least one order followed by at least 10 consecutive days with no events and then a later event, reporting only the earliest such gap per user. In the sample data, user 4 ordered on 2025-08-20, had no events from 2025-08-21 through 2025-08-30 (a 10-day inactivity window is satisfied and actually extends longer), and then had an event on 2025-09-10, yielding 20 gap days between order and next event.

first_post_gap_event_dategap_dayslast_order_dateuser_id
2025-09-10202025-08-204

Hints

  1. Join orders to events on the same user with different date conditions to identify events within the first 10 days after an order and those strictly after that window.
  2. Use ROW_NUMBER() to pick the first event after the 10-day gap for each order, filter out orders that have any events in days t+1..t+10, then apply another ROW_NUMBER() partitioned by user to keep only the earliest qualifying gap per user.