The request

From Thomas Whitaker, Head of Merchandising
Your goal
Step 1 of 2. Check the change. For every order line of the products whose price rose, label period ('before' if order_date < '2025-03-01', else 'after') and price_paid ('old' if the line's unit_price equals the product's lowest price, else 'new'). Return period, price_paid and lines (the number of order lines), sorted by period descending, then price_paid descending. Use all order lines, whatever the status.
- Read from the
orderstablenot done yet - Read from the
order_itemstablenot done yet - Show exactly
periodprice_paidlinesnot done yet
Step 2 of 2. Compare like for like. Use completed orders, lines with quantity > 0, and only products launched before 2024-01-01. Label each line price_group 'raised' or 'unchanged'. Return price_group, units_2024 and units_2025 (total quantity from March 1 to August 31 of each year), units_change_pct, revenue_2024 and revenue_2025 (net revenue, rounded to 2) and revenue_change_pct. Both change columns are (2025 - 2024) * 100 / 2024 from unrounded sums, rounded to 1. Sort by price_group.
- Read from the
productstablenot done yet - Read from the
orderstablenot done yet - Show exactly
price_groupunits_2024units_2025units_change_pctrevenue_2024revenue_2025revenue_change_pctnot done yet
Step 1 · 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.
Step 2 · 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.
What Thomas gets, and why
Tables
Harbor & Pine store data, ready to query.
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 |
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 |
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
What you are checking, and why
Before you compare anything, prove that you are looking at the right products and the right date.
Thomas says “about 30 products, March 1”. The data can confirm both, because order_items stores
the price paid on every line, while products.unit_price only holds today’s list price.
A product whose price changed has two different prices across its order lines. That is a
grouped condition, so it belongs in HAVING:
SELECT product_id, MIN(unit_price) AS old_price, MAX(unit_price) AS new_price
FROM order_items
GROUP BY product_id
HAVING MIN(unit_price) <> MAX(unit_price);
This returns 29 products. The check in step 1 then labels each of their lines twice, by date and by price, and counts the combinations. If the change happened cleanly on March 1, only two combinations exist: before with the old price, after with the new one. Any other row is a finding in itself.
Like for like, with a control group
Step 2 answers the agreed question. Three choices make the comparison fair:
- Same months, a year apart. March to August 2025 against March to August 2024. The season is the same on both sides.
- Like-for-like products. Only products launched before January 1, 2024, so every product was on sale for the whole of both windows.
- A control group. Every other product is labelled
unchanged. Its growth is the baseline: what the store did anyway.
The pattern for two periods side by side is conditional aggregation, the same one used for year-over-year reports:
SUM(CASE WHEN yr = 2024 THEN quantity END) AS u24,
SUM(CASE WHEN yr = 2025 THEN quantity END) AS u25
Keep these raw sums in a CTE and compute the percentage in the final SELECT, so it is calculated
from unrounded numbers.
Reading the result
| price_group | units_change_pct | revenue_change_pct |
|---|---|---|
| raised | 21.0 | 44.3 |
| unchanged | 9.2 | 13.4 |
If the rise had hurt demand, raised products would grow less than unchanged ones. They grew more. Revenue grew faster than units for raised products because each unit earned about 6% more and because sales shifted towards the dearer items in the group.
Pitfalls
BETWEEN '2025-03-01' AND '2025-08-31'on a timestamp drops the evening of August 31; use a half-open range.- Counting
cancelledorrefundedorders adds sales that never happened. IN (SELECT ...)is fine for 29 ids; on millions of rows, a join to the CTE is the faster habit.
Lantern lit!
Practised: data validationhavingcteconditional aggregationlike for likecontrol groupyoy
- Solved
- No hints
- First run
