Coding questions · Netflix
Write SQL for rolling frequency caps
This question evaluates proficiency with rolling-window analytics, frequency cap logic, time-based attribution, and per-user/per-campaign aggregation for advertising data.
You work on ad serving with per-user rolling 7-day frequency caps at the campaign level. Assume "today" is 2025-09-01 and t0 = 2025-09-01 12:00:00 UTC. Write SQL (any dialect with window functions and common table expressions) to answer parts (A)-(C) exactly using the schema and sample data below.
Schema:
- users(user_id INT PRIMARY KEY, household_id TEXT, country TEXT)
- campaigns(campaign_id INT PRIMARY KEY, cap_7d INT, start_date DATE, end_date DATE)
- impressions(impression_id INT PRIMARY KEY, ts TIMESTAMP, user_id INT, campaign_id INT)
- clicks(click_id INT PRIMARY KEY, impression_id INT, ts TIMESTAMP)
- conversions(conv_id INT PRIMARY KEY, click_id INT, ts TIMESTAMP, revenue DECIMAL(10,2))
Sample data (ASCII):
users
user_id | household_id | country
1 | A | US
2 | A | US
3 | B | CA
4 | C | US
campaigns
campaign_id | cap_7d | start_date | end_date
10 | 3 | 2025-08-15 | 2025-09-30
20 | 5 | 2025-08-20 | 2025-09-15
impressions
impression_id | ts | user_id | campaign_id
100 | 2025-08-26 10:00 | 1 | 10
101 | 2025-08-30 09:00 | 1 | 10
102 | 2025-08-31 09:10 | 1 | 10
103 | 2025-09-01 08:00 | 1 | 10
104 | 2025-08-28 12:00 | 2 | 10
105 | 2025-08-30 13:00 | 2 | 10
106 | 2025-08-26 14:00 | 3 | 20
107 | 2025-08-29 15:00 | 3 | 20
108 | 2025-08-30 16:00 | 3 | 20
109 | 2025-09-01 11:00 | 4 | 10
clicks
click_id | impression_id | ts
1000 | 101 | 2025-08-30 09:05
1001 | 102 | 2025-08-31 09:12
1002 | 108 | 2025-08-30 16:01
1003 | 103 | 2025-09-01 08:01
conversions
conv_id | click_id | ts | revenue
2000 | 1000 | 2025-08-30 10:00 | 5.00
2001 | 1001 | 2025-09-01 10:00 | 0.00
2002 | 1002 | 2025-09-01 12:00 | 20.00
2003 | 1003 | 2025-09-01 13:00 | 8.00
Assumptions: (i) Only impressions within a campaign’s [start_date, end_date] are valid; (ii) A rolling 7-day window at time t includes [t-7 days, t); (iii) An impression beyond the cap is the (cap_7d+1)-th, (cap_7d+2)-th, etc., within the 7-day window; (iv) Revenue is attributed to the impression via click->conversion chain; (v) If multiple conversions follow a click, sum their revenue.
Tasks: (A) For each campaign at t0, output: campaign_id, users_at_cap (count of users whose last-7-day impression count equals cap_7d), users_one_below_cap (count with last-7-day count = cap_7d-1), and pct_over_cap_if_one_more (percentage of active users in last 7 days who would exceed the cap if each received one more impression at t0). (B) For every (user_id, campaign_id) seen in the last 7 days, compute next_eligible_time: the earliest timestamp > t0 when serving an additional impression would not breach the 7-day cap because the oldest in-window impression has rolled out. Return user_id, campaign_id, next_eligible_time. (C) For 2025-08-25 12:00 <= ts < 2025-09-01 12:00, compute, per campaign, ROI_beyond_cap = (revenue from impressions beyond cap within the window) / (count of impressions beyond cap within the window). Return campaign_id, impressions_beyond_cap, revenue_beyond_cap, ROI_beyond_cap. Edge cases must be handled correctly: users with 0 clicks/conversions, multiple conversions per click, impressions straddling campaign end_date, and users with impressions both before and after the window boundary.
Rolling 7-day frequency caps at campaign level (Part A: cap-status summary at t0)
You work on ad serving with per-user rolling 7-day frequency caps at the campaign level.
Assume:
- Fixed current date is 2025-06-01.
- t0 = TIMESTAMP '2025-06-01 12:00:00' UTC.
- A rolling 7-day window at time t includes [t - 7 days, t).
- Only impressions within a campaign’s [start_date, end_date] (inclusive) are valid.
Task (A) For each campaign at t0, compute metrics using impressions in the last 7 days window [2025-05-25 12:00:00, 2025-06-01 12:00:00): Return:
- campaign_id
- users_at_cap: count of users whose last-7-day impression count equals cap_7d
- users_one_below_cap: count of users whose last-7-day impression count equals cap_7d - 1
- pct_over_cap_if_one_more: percentage of active users (users with >=1 valid impression in the last 7 days for that campaign) who would exceed the cap if they each received one additional impression at exactly t0. (A user would exceed if their current last-7-day count is >= cap_7d.)
Notes:
- Consider each campaign independently.
- Output percentages as a number from 0 to 100 (rounded to 2 decimals).
Table users
| column | type | constraints |
|---|---|---|
| user_id | INT | PRIMARY KEY, NOT NULL |
| household_id | VARCHAR(10) | NOT NULL |
| country | VARCHAR(2) | NOT NULL |
Sample data:
| country | household_id | user_id |
|---|---|---|
| US | A | 1 |
| US | A | 2 |
| CA | B | 3 |
| US | C | 4 |
Table campaigns
| column | type | constraints |
|---|---|---|
| campaign_id | INT | PRIMARY KEY, NOT NULL |
| cap_7d | INT | NOT NULL |
| start_date | DATE | NOT NULL |
| end_date | DATE | NOT NULL |
Sample data:
| campaign_id | cap_7d | end_date | start_date |
|---|---|---|---|
| 10 | 3 | 2025-06-30 | 2025-05-15 |
| 20 | 5 | 2025-06-15 | 2025-05-20 |
Table impressions
| column | type | constraints |
|---|---|---|
| impression_id | INT | PRIMARY KEY, NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| user_id | INT | NOT NULL |
| campaign_id | INT | NOT NULL |
Sample data:
| campaign_id | impression_id | ts | user_id |
|---|---|---|---|
| 10 | 100 | 2025-05-26 10:00:00 | 1 |
| 10 | 101 | 2025-05-30 09:00:00 | 1 |
| 10 | 102 | 2025-05-31 09:10:00 | 1 |
| 10 | 103 | 2025-06-01 08:00:00 | 1 |
| 10 | 104 | 2025-05-28 12:00:00 | 2 |
| 10 | 105 | 2025-05-30 13:00:00 | 2 |
| 20 | 106 | 2025-05-26 14:00:00 | 3 |
| 20 | 107 | 2025-05-29 15:00:00 | 3 |
| 20 | 108 | 2025-05-30 16:00:00 | 3 |
| 10 | 109 | 2025-06-01 11:00:00 | 4 |
Table clicks
| column | type | constraints |
|---|---|---|
| click_id | INT | PRIMARY KEY, NOT NULL |
| impression_id | INT | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
Sample data:
| click_id | impression_id | ts |
|---|---|---|
| 1000 | 101 | 2025-05-30 09:05:00 |
| 1001 | 102 | 2025-05-31 09:12:00 |
| 1002 | 108 | 2025-05-30 16:01:00 |
| 1003 | 103 | 2025-06-01 08:01:00 |
Table conversions
| column | type | constraints |
|---|---|---|
| conv_id | INT | PRIMARY KEY, NOT NULL |
| click_id | INT | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| revenue | DECIMAL(10,2) | NOT NULL |
Sample data:
| click_id | conv_id | revenue | ts |
|---|---|---|---|
| 1000 | 2000 | 5 | 2025-05-30 10:00:00 |
| 1001 | 2001 | 0 | 2025-06-01 10:00:00 |
| 1002 | 2002 | 20 | 2025-06-01 11:30:00 |
| 1003 | 2003 | 8 | 2025-06-01 13:00:00 |
| 1003 | 2004 | 2 | 2025-06-02 09:00:00 |
Expected output
One row per campaign with counts of users at cap / one below cap, and the percent of active users who would be over-cap if they each received one additional impression at t0.
| campaign_id | pct_over_cap_if_one_more | users_at_cap | users_one_below_cap |
|---|---|---|---|
| 10 | 33.33 | 0 | 1 |
| 20 | 0 | 0 | 0 |
Hints
- Compute per-(user_id, campaign_id) impression counts in [t0-7d, t0).
- Active users are those with at least one valid impression in the window.
- A user would exceed after one more impression at t0 iff current_count >= cap_7d.
Rolling 7-day frequency caps: next eligible time per user-campaign
You work on ad serving with per-user rolling 7-day frequency caps at the campaign level.
Assume:
t0 = TIMESTAMP '2025-06-01 12:00:00'UTC.- A rolling 7-day window at time
tincludes[t - 7 days, t). - Only impressions within a campaign's inclusive
[start_date, end_date]are valid. - An impression at timestamp
xrolls out of the window at exactlyx + INTERVAL '7 days'.
For every (user_id, campaign_id) with at least one valid impression in [2025-05-25 12:00:00, 2025-06-01 12:00:00), compute next_eligible_time: the earliest timestamp at or after t0 when serving one additional impression would not breach that campaign's cap_7d.
Return user_id, campaign_id, and next_eligible_time formatted as YYYY-MM-DD HH24:MI:SS, ordered by user_id, then campaign_id.
Table users
| column | type | constraints |
|---|---|---|
| user_id | INT | PRIMARY KEY, NOT NULL |
| household_id | VARCHAR(10) | NOT NULL |
| country | VARCHAR(2) | NOT NULL |
Sample data:
| country | household_id | user_id |
|---|---|---|
| US | A | 1 |
| US | A | 2 |
| CA | B | 3 |
| US | C | 4 |
Table campaigns
| column | type | constraints |
|---|---|---|
| campaign_id | INT | PRIMARY KEY, NOT NULL |
| cap_7d | INT | NOT NULL |
| start_date | DATE | NOT NULL |
| end_date | DATE | NOT NULL |
Sample data:
| campaign_id | cap_7d | end_date | start_date |
|---|---|---|---|
| 10 | 3 | 2025-06-30 | 2025-05-15 |
| 20 | 5 | 2025-06-15 | 2025-05-20 |
Table impressions
| column | type | constraints |
|---|---|---|
| impression_id | INT | PRIMARY KEY, NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| user_id | INT | NOT NULL |
| campaign_id | INT | NOT NULL |
Sample data:
| campaign_id | impression_id | ts | user_id |
|---|---|---|---|
| 10 | 100 | 2025-05-26 10:00:00 | 1 |
| 10 | 101 | 2025-05-30 09:00:00 | 1 |
| 10 | 102 | 2025-05-31 09:10:00 | 1 |
| 10 | 103 | 2025-06-01 08:00:00 | 1 |
| 10 | 104 | 2025-05-28 12:00:00 | 2 |
| 10 | 105 | 2025-05-30 13:00:00 | 2 |
| 20 | 106 | 2025-05-26 14:00:00 | 3 |
| 20 | 107 | 2025-05-29 15:00:00 | 3 |
| 20 | 108 | 2025-05-30 16:00:00 | 3 |
| 10 | 109 | 2025-06-01 11:00:00 | 4 |
Table clicks
| column | type | constraints |
|---|---|---|
| click_id | INT | PRIMARY KEY, NOT NULL |
| impression_id | INT | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
Sample data:
| click_id | impression_id | ts |
|---|---|---|
| 1000 | 101 | 2025-05-30 09:05:00 |
| 1001 | 102 | 2025-05-31 09:12:00 |
| 1002 | 108 | 2025-05-30 16:01:00 |
| 1003 | 103 | 2025-06-01 08:01:00 |
Table conversions
| column | type | constraints |
|---|---|---|
| conv_id | INT | PRIMARY KEY, NOT NULL |
| click_id | INT | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| revenue | DECIMAL(10,2) | NOT NULL |
Sample data:
| click_id | conv_id | revenue | ts |
|---|---|---|---|
| 1000 | 2000 | 5 | 2025-05-30 10:00:00 |
| 1001 | 2001 | 0 | 2025-06-01 10:00:00 |
| 1002 | 2002 | 20 | 2025-06-01 11:30:00 |
| 1003 | 2003 | 8 | 2025-06-01 13:00:00 |
| 1003 | 2004 | 2 | 2025-06-02 09:00:00 |
Expected output
One row per (user_id, campaign_id) that has at least one valid impression in the last 7 days, showing the earliest time at/after t0 that an additional impression would not breach cap_7d.
| campaign_id | next_eligible_time | user_id |
|---|---|---|
| 10 | 2025-06-06 09:00:00 | 1 |
| 10 | 2025-06-01 12:00:00 | 2 |
| 20 | 2025-06-01 12:00:00 | 3 |
| 10 | 2025-06-01 12:00:00 | 4 |
Hints
- If the current in-window count is below
cap_7d, the next eligible time ist0. - If the count is at or above the cap, rank the in-window impressions from oldest to newest and find the impression that must roll out.
- Use
TO_CHAR(..., 'YYYY-MM-DD HH24:MI:SS')for the final timestamp display.
Rolling 7-day frequency caps (Part C: ROI of impressions beyond cap)
You work on ad serving with per-user rolling 7-day frequency caps at the campaign level.
Assume:
- Fixed current date is 2025-06-01.
- t0 = TIMESTAMP '2025-06-01 12:00:00' UTC.
- A rolling 7-day window at time t includes [t - 7 days, t).
- Only impressions within a campaign’s [start_date, end_date] (inclusive) are valid.
- An impression is considered "beyond cap" if, at that impression’s timestamp, it is the (cap_7d+1)-th, (cap_7d+2)-th, etc. impression for that (user_id, campaign_id) within the prior 7 days.
- Revenue is attributed to the impression through the impression -> click -> conversion chain.
- If multiple conversions follow the same click, sum their revenue.
- For this task, include all attributed revenue for those impressions regardless of conversion timestamp.
Task (C) For impressions with 2025-05-25 12:00:00 <= impression.ts < 2025-06-01 12:00:00, compute per campaign:
- impressions_beyond_cap: number of impressions in that impression-ts window that are beyond cap
- revenue_beyond_cap: total revenue attributed to those beyond-cap impressions
- roi_beyond_cap = revenue_beyond_cap / impressions_beyond_cap (NULL if impressions_beyond_cap = 0)
Return: campaign_id, impressions_beyond_cap, revenue_beyond_cap, roi_beyond_cap.
Table users
| column | type | constraints |
|---|---|---|
| user_id | INT | PRIMARY KEY, NOT NULL |
| household_id | VARCHAR(10) | NOT NULL |
| country | VARCHAR(2) | NOT NULL |
Sample data:
| country | household_id | user_id |
|---|---|---|
| US | A | 1 |
| US | A | 2 |
| CA | B | 3 |
| US | C | 4 |
Table campaigns
| column | type | constraints |
|---|---|---|
| campaign_id | INT | PRIMARY KEY, NOT NULL |
| cap_7d | INT | NOT NULL |
| start_date | DATE | NOT NULL |
| end_date | DATE | NOT NULL |
Sample data:
| campaign_id | cap_7d | end_date | start_date |
|---|---|---|---|
| 10 | 3 | 2025-06-30 | 2025-05-15 |
| 20 | 5 | 2025-06-15 | 2025-05-20 |
Table impressions
| column | type | constraints |
|---|---|---|
| impression_id | INT | PRIMARY KEY, NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| user_id | INT | NOT NULL |
| campaign_id | INT | NOT NULL |
Sample data:
| campaign_id | impression_id | ts | user_id |
|---|---|---|---|
| 10 | 100 | 2025-05-26 10:00:00 | 1 |
| 10 | 101 | 2025-05-30 09:00:00 | 1 |
| 10 | 102 | 2025-05-31 09:10:00 | 1 |
| 10 | 103 | 2025-06-01 08:00:00 | 1 |
| 10 | 104 | 2025-05-28 12:00:00 | 2 |
| 10 | 105 | 2025-05-30 13:00:00 | 2 |
| 20 | 106 | 2025-05-26 14:00:00 | 3 |
| 20 | 107 | 2025-05-29 15:00:00 | 3 |
| 20 | 108 | 2025-05-30 16:00:00 | 3 |
| 10 | 109 | 2025-06-01 11:00:00 | 4 |
Table clicks
| column | type | constraints |
|---|---|---|
| click_id | INT | PRIMARY KEY, NOT NULL |
| impression_id | INT | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
Sample data:
| click_id | impression_id | ts |
|---|---|---|
| 1000 | 101 | 2025-05-30 09:05:00 |
| 1001 | 102 | 2025-05-31 09:12:00 |
| 1002 | 108 | 2025-05-30 16:01:00 |
| 1003 | 103 | 2025-06-01 08:01:00 |
Table conversions
| column | type | constraints |
|---|---|---|
| conv_id | INT | PRIMARY KEY, NOT NULL |
| click_id | INT | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| revenue | DECIMAL(10,2) | NOT NULL |
Sample data:
| click_id | conv_id | revenue | ts |
|---|---|---|---|
| 1000 | 2000 | 5 | 2025-05-30 10:00:00 |
| 1001 | 2001 | 0 | 2025-06-01 10:00:00 |
| 1002 | 2002 | 20 | 2025-06-01 11:30:00 |
| 1003 | 2003 | 8 | 2025-06-01 13:00:00 |
| 1003 | 2004 | 2 | 2025-06-02 09:00:00 |
Expected output
One row per campaign that appears in the impression-ts analysis window. Shows how many impressions were beyond cap, their attributed revenue, and revenue per beyond-cap impression (NULL when count is 0).
| campaign_id | impressions_beyond_cap | revenue_beyond_cap | roi_beyond_cap |
|---|---|---|---|
| 10 | 1 | 10 | 10 |
| 20 | 0 | 0 | NULL |
Hints
- To label an impression as beyond-cap, compare cap_7d to the count of impressions for the same user+campaign in [ts-7d, ts).
- Compute revenue per impression by aggregating conversions per click and then per impression_id.
- Use LEFT JOINs and COALESCE to handle impressions with no clicks/conversions.