The request

From Rachel Adams, Customer Support Lead
Your goal
Use order lines of completed orders placed in 2025, excluding lines with quantity <= 0. Return one row per category with: category, lines (order lines), returned_lines (lines that have a row in returns), return_rate_pct (returned_lines / lines * 100, rounded to 1) and total_check (true when the sum of lines across all categories equals the number of clean lines). Sort by return_rate_pct descending, then category.
- Read from the
productstablenot done yet - Read from the
orderstablenot done yet - Show exactly
categorylinesreturned_linesreturn_rate_pcttotal_checknot done yet
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 Rachel 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 |
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 |
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 |
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
Every analysis follows roughly the same path, whatever the tool. Writing it down as stages makes the SQL easier to build, easier to review and much easier to defend when someone questions the number.
The stages
- Understand the request. What decision does it feed? Here: staffing for returns. What exactly is counted? A return rate per category, as a share of order lines, for 2025.
- Explore keys and grain.
order_itemshas one row per line;returnshas at most one row perorder_item_id;productsgives the category. The grain of the answer is one row per category. - Extract the rows in scope: lines of completed orders placed in 2025.
- Clean: drop the lines with quantity of zero or below.
- Aggregate to the requested grain.
- Structure the steps as named CTEs so each one can be run and checked alone.
- Analyze further if needed (ranks, shares, trends with window functions).
- Validate: do the parts add up to the whole? Are row counts plausible at each step?
- Present: a sorted table with a sentence of interpretation.
The query, stage by stage
The starter is stage 3. Running it alone returns 10,810 lines, the first sanity check. The cleaning
CTE removes 10 lines with a bad quantity, leaving 10,800. The aggregate joins products and LEFT JOINs
returns: a LEFT JOIN, because lines without a return must stay in the denominator, and
COUNT(r.return_id) counts only the matched ones.
The validation is a column, not a separate memo:
SUM(lines) OVER () = (SELECT COUNT(*) FROM clean_lines) AS total_check
SUM(lines) OVER () adds the six category counts; the subquery counts the clean lines directly. If a
product were missing a category, or a join duplicated lines, the two would differ and the column would
say false.
The result: Textiles 4.2% (92 of 2,195 lines), Lighting 3.9%, Outdoor 3.7%, Furniture 3.6%, Decor 2.9% and Kitchen 2.0% (51 of 2,548). Furniture has the fewest lines (586), so its rate moves the most with a handful of returns; say so when you present it.
Validate before you present
Useful checks for almost any analysis:
- Row counts after every CTE, compared with what you expect from the data documentation.
- Parts versus whole, as above.
- One entity traced end to end: pick one order and confirm its lines and returns by hand.
- A comparison with a number someone already trusts, such as last month’s report.
Common mistakes
- Skipping stage 1 and answering a different question: returns per order instead of per line, or refund dollars instead of counts.
- INNER JOIN to returns, which makes every rate 100%.
- Including refunded orders, whose lines are all returned by definition, without saying so.
- Presenting a rate without its denominator.
In SQL Server
The CTE chain, LEFT JOIN and COUNT(column) are identical in T-SQL. Two differences: a boolean
expression cannot be a column, so write the check as
CASE WHEN SUM(lines) OVER () = (SELECT COUNT(*) FROM clean_lines) THEN 1 ELSE 0 END, and
100.0 * returned_lines / lines needs the 100.0, because / on two integers truncates in SQL Server.
Lantern lit!
Practised: analyst workflowctegrainvalidationreturn rate
- Solved
- No hints
- First run
