The request

From Laura Bennett, Head of Finance
Your goal
Using completed orders and lines with quantity > 0, return one row per month for all months in the data with: month (first day of the month, as a DATE), net_revenue (sum of quantity * unit_price * (1 - discount_pct / 100.0), rounded to 2), ytd_revenue (running total of net revenue from January of the same year through that month, rounded to 2) and cumulative_revenue (running total from the first month in the data through that month, rounded to 2). Sort by month ascending.
- Read from the
orderstablenot done yet - Read from the
order_itemstablenot done yet - Show exactly
monthnet_revenueytd_revenuecumulative_revenuenot done yet
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 Laura 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 |
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 |
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 |
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
A running total answers “how much so far?”. Finance rarely wants “so far, ever” though. They want “so far this year”, which starts at zero every January. That is one extra clause.
Two running totals, one difference
You met SUM(...) OVER (ORDER BY month) in the window functions chapter: with an ORDER BY inside
OVER, each row sums every row up to and including itself. Add PARTITION BY and the sum restarts
in every partition:
SUM(net_revenue) OVER (PARTITION BY YEAR(month) ORDER BY month) AS ytd_revenue,
SUM(net_revenue) OVER (ORDER BY month) AS cumulative_revenue
YEAR(month) is 2023, 2024 or 2025, so the 36 monthly rows fall into three windows of twelve. The
partition does not have to be a column in the output. Any expression works.
Worked example: the turn of the year
With completed orders and lines with a positive quantity, the rows around January 2024 look like this:
| month | net_revenue | ytd_revenue | cumulative_revenue |
|---|---|---|---|
| 2023-12-01 | 241,527.32 | 1,600,531.40 | 1,600,531.40 |
| 2024-01-01 | 109,305.64 | 109,305.64 | 1,709,837.04 |
| 2024-02-01 | 77,546.58 | 186,852.21 | 1,787,383.61 |
December 2023’s year-to-date is the full 2023 total. January 2024 starts over, while the lifetime column carries on. By December 2025 the lifetime total is $5,896,296.81 and the 2025 year-to-date is $2,301,690.14.
Reading year-to-date
Year-to-date is most useful against the same point last year. At the end of June, 2025 stood at $1,026,835.51 against $789,588.04 for 2024, about 30% ahead. Be careful extrapolating a half year to a full year at Harbor & Pine: November and December alone brought in 25% of 2025’s revenue, so a store that is “half way” in June is not half way to its annual number.
Aggregate first, then window
Window functions run after GROUP BY. Sum the order lines to months in a CTE, then run the window
over 36 rows. Writing SUM(SUM(...)) OVER (...) in one query also works, but the CTE is easier to
read and to check.
Common mistakes
- Partitioning by the wrong thing.
PARTITION BY monthgives every row its own window, so the running total equals the monthly value. - Forgetting ORDER BY inside OVER.
SUM(net_revenue) OVER (PARTITION BY YEAR(month))returns the full-year total on every row, not a running one. - Gaps in months. A running total over a month with no sales simply skips that month. Here every month has sales; on sparse data, build a calendar first.
In SQL Server
There is no DATE_TRUNC before SQL Server 2022 (DATETRUNC(month, order_date) in 2022+). Use
DATEFROMPARTS(YEAR(order_date), MONTH(order_date), 1). Also add an explicit frame: the default is
RANGE, which is slower and treats tied dates as one step.
SUM(net_revenue) OVER (PARTITION BY YEAR(month) ORDER BY month
ROWS UNBOUNDED PRECEDING) AS ytd_revenue Lantern lit!
Practised: running totalpartition bysum overyear to datecte
- Solved
- No hints
- First run
