The request

From Catherine Hale, Chief Executive Officer
Your goal
Step 1 of 3. For completed orders placed in 2025, return one row per category with: category, net_revenue (rounded to 2) and pct_of_total (the category's share of total 2025 net revenue across all categories, times 100, rounded to 1). Sort by net_revenue descending.
- Read from the
productstablenot done yet - Read from the
orderstablenot done yet - Show exactly
categorynet_revenuepct_of_totalnot done yet
Step 2 of 3. Number each customer's completed orders by order_date (ties broken by order_id). For customers who have a second completed order, return one row per segment (from customers) with: segment, customers (number of customers with a second order) and avg_days_to_second (average days between the calendar dates of the first and second completed orders, rounded to 1).
- Read from the
customerstablenot done yet - Read from the
orderstablenot done yet - Show exactly
segmentcustomersavg_days_to_secondnot done yet
Step 3 of 3. Assign each customer to the month of their first completed order (cohort_month, a DATE). For cohorts from January to June 2025, return: cohort_month, cohort_size (customers in the cohort), retained_m1 (cohort customers with at least one completed order in the calendar month right after the cohort month) and retention_m1_pct (retained_m1 / cohort_size * 100, rounded to 1). Sort by cohort_month ascending.
- Read from the
orderstablenot done yet - Show exactly
cohort_monthcohort_sizeretained_m1retention_m1_pctnot done yet - 6 rows, sorted as askednot done yet
Step 1 · your query
Loading the editor…
Reference solution
| product_id | name | category | subcategory | unit_price |
|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
Step 2 · your query
Loading the editor…
Reference solution
| customer_id | first_name | last_name | city | |
|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
Step 3 · your query
Loading the editor…
Reference solution
| order_id | customer_id | order_date | status | channel |
|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
What Catherine gets, and why
Tables
Harbor & Pine store data, ready to query.
products120 rows · 8 columns
product_idintegernamevarcharcategoryvarcharsubcategoryvarcharunit_pricedecimalunit_costdecimallaunched_ondateis_activeboolean
| product_id | name | category | subcategory | unit_price | unit_cost | launched_on | is_active |
|---|---|---|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 | NULL | 2023-09-11 | true |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 | 503.50 | 2024-05-16 | true |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 | 429.87 | 2023-05-27 | true |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 | 214.70 | 2022-12-20 | true |
| 105 | Avery Leather Bed Frame | Furniture | Beds | 449.00 | 232.70 | 2021-08-31 | true |
orders12,000 rows · 7 columns
order_idintegercustomer_idintegerorder_datetimestampstatusvarcharchannelvarcharshipping_feedecimalcoupon_codevarchar
| order_id | customer_id | order_date | status | channel | shipping_fee | coupon_code |
|---|---|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web | 0.00 | NULL |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web | 0.00 | NULL |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace | 4.99 | WELCOME10 |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace | 4.99 | WELCOME10 |
| 1005 | 82 | 2023-01-02 11:43:36 | completed | web | 0.00 | WELCOME10 |
order_items30,338 rows · 6 columns
order_item_idintegerorder_idintegerproduct_idintegerquantityintegerunit_pricedecimaldiscount_pctdecimal
| order_item_id | order_id | product_id | quantity | unit_price | discount_pct |
|---|---|---|---|---|---|
| 1 | 1001 | 121 | 9 | 31.99 | 0.00 |
| 2 | 1001 | 154 | 3 | 11.99 | 0.00 |
| 3 | 1002 | 154 | 4 | 11.99 | 0.00 |
| 4 | 1002 | 194 | 1 | 149.00 | 0.00 |
| 5 | 1002 | 193 | 2 | 19.99 | 0.00 |
customers2,018 rows · 9 columns
customer_idintegerfirst_namevarcharlast_namevarcharemailvarcharcityvarcharstatevarcharsignup_datedatesegmentvarcharreferral_sourcevarchar
| customer_id | first_name | last_name | city | state | signup_date | segment | referral_source | |
|---|---|---|---|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco | CA | 2022-06-02 | consumer | search |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta | GA | 2022-06-02 | consumer | NULL |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles | CA | 2022-06-04 | business | NULL |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle | WA | 2022-06-06 | business | |
| 5 | Kyle | Jimenez | kjimenez27@yahoo.com | Indianapolis | IN | 2022-06-07 | consumer | NULL |
returns1,673 rows · 5 columns
return_idintegerorder_item_idintegerreturned_ondatereasonvarcharrefund_amountdecimal
| return_id | order_item_id | returned_on | reason | refund_amount |
|---|---|---|---|---|
| 1 | 14 | 2023-01-10 | damaged | 83.69 |
| 2 | 42 | 2023-01-10 | wrong_item | 19.99 |
| 3 | 105 | 2023-01-14 | wrong_item | 10.99 |
| 4 | 24 | 2023-01-16 | changed_mind | 26.99 |
| 5 | 126 | 2023-01-16 | late | 20.99 |
employees60 rows · 8 columns
employee_idintegerfirst_namevarcharlast_namevarchardepartmentvarchartitlevarcharmanager_idintegerhire_datedatesalaryinteger
| employee_id | first_name | last_name | department | title | manager_id | hire_date | salary |
|---|---|---|---|---|---|---|---|
| 1 | Catherine | Hale | Executive | Chief Executive Officer | NULL | 2016-03-01 | 285000 |
| 2 | Robert | Kim | Executive | Chief Operating Officer | 1 | 2017-01-09 | 235000 |
| 3 | Megan | Collins | Sales | Head of Sales | 1 | 2018-04-16 | 172000 |
| 4 | Chloe | Gutierrez | Sales | Account Executive | 3 | 2020-01-21 | 72000 |
| 5 | Frank | Bell | Sales | Account Executive | 3 | 2023-05-21 | 73000 |
web_sessions40,000 rows · 7 columns
session_idintegercustomer_idintegerstarted_attimestampdevicevarcharsourcevarcharpages_viewedintegerconvertedboolean
| session_id | customer_id | started_at | device | source | pages_viewed | converted |
|---|---|---|---|---|---|---|
| 1 | NULL | 2023-01-01 23:02:24 | mobile | paid | 1 | false |
| 2 | 18 | 2023-01-01 15:07:49 | desktop | paid | 1 | false |
| 3 | NULL | 2023-01-01 16:55:46 | desktop | direct | 7 | false |
| 4 | NULL | 2023-01-01 12:43:28 | mobile | organic | 3 | false |
| 5 | NULL | 2023-01-01 13:06:21 | mobile | social | 2 | false |
marketing_spend3,276 rows · 3 columns
spend_datedatechannelvarcharamountdecimal
| spend_date | channel | amount |
|---|---|---|
| 2023-01-01 | paid | 492.99 |
| 2023-01-01 | 37.38 | |
| 2023-01-01 | social | 200.53 |
| 2023-01-02 | paid | 709.13 |
| 2023-01-02 | 64.13 |
The lesson
Cohort analysis is the classic analyst take-home: it needs CTEs, window functions, date logic and a clear definition, all at once. This boss builds up to it.
Step 1: share of total
Aggregate per category, then divide each row by the grand total:
ROUND(100.0 * net_revenue / SUM(net_revenue) OVER (), 1) AS pct_of_total
An empty OVER () makes the window the whole result, so every row sees the same total. No subquery, no
join. The shares should add up to about 100; if they add to far more, a join fanned out.
Step 2: time to second order
ROW_NUMBER numbers each customer’s orders in time order. LAG then gives each order’s gap from the
previous one, and keeping order_number = 2 leaves exactly one row per returning customer: the gap
between their first and second orders. The order_id tie-breaker guards against two orders with the
same timestamp.
The starter shows one customer’s numbered orders. Inspecting a single entity like this is how you sanity check a window before aggregating it.
Customers who never placed a second order are not in the average. That is correct for “how long until they come back”, but present it next to how many never came back at all, or the number looks better than reality.
Step 3: cohort retention
Three definitions make a cohort table:
- Cohort: the month of a customer’s first completed order.
MIN(order_month) OVER (PARTITION BY customer_id)attaches it to every one of their rows without collapsing them. - Activity: a customer is active in a month if they placed at least one completed order. The
customer_monthsCTE usesSELECT DISTINCTon purpose, to get one row per customer per month, the correct grain for “active or not”. - Retained in month 1: active in the calendar month right after the cohort month.
COUNT(DISTINCT CASE WHEN order_month = CAST(cohort_month + INTERVAL '1 month' AS DATE)
THEN customer_id END) AS retained_m1
COUNT(DISTINCT CASE ...) counts customers who meet the condition. The CASE returns NULL otherwise, and
COUNT ignores NULLs.
The cohort filter is applied after the window, in the outer query. Filtering orders to 2025 before computing MIN would assign customers who first bought in 2024 to a 2025 cohort, the same trap as with LAG and moving averages.
Dialect notes
Adding a month: DuckDB and Postgres accept date + INTERVAL '1 month' (the result is a timestamp, hence
the cast). BigQuery uses DATE_ADD(cohort_month, INTERVAL 1 MONTH), MySQL
DATE_ADD(cohort_month, INTERVAL 1 MONTH), Snowflake DATEADD(month, 1, cohort_month).
Taking it further
The full cohort table has a column for every month offset (m1, m2, m3, …), usually computed with
DATEDIFF in months between order_month and cohort_month, grouped by cohort and offset, then
pivoted. In an interview, getting month 1 right, with clear definitions stated out loud, is worth more
than a rushed full grid.
Lantern lit!
Practised: window functionsrow numberlagsum overcohort retentioncte
- Solved
- No hints
- First run
