The request

From Kevin Brooks, Operations Manager
Your goal
From orders (all statuses), count orders per calendar day for every day. Return only the days of December 2025 with: order_day (a DATE), orders (that day's count) and orders_7d_avg (the average of daily orders over that day and the 6 days before it, rounded to 1; the first days of December use late November in their window). Sort by order_day ascending.
- Read from the
orderstablenot done yet - Show exactly
order_dayordersorders_7d_avgnot done yet - 31 rows, sorted as askednot 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 Kevin 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 |
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 |
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 |
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 moving average smooths noisy daily numbers by averaging each day with its neighbors. Day-of-week swings and one-off spikes flatten out, and the trend becomes visible. Operations, marketing and product teams use 7-day averages constantly, because a 7-day window always contains exactly one of each weekday.
The frame clause
A window frame says exactly which rows around the current one the function should see:
AVG(orders) OVER (
ORDER BY order_day
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS orders_7d_avg
6 PRECEDING AND CURRENT ROW is seven rows: today and the six before. Other frames you will use:
| frame | meaning |
|---|---|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
running total or running average |
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING |
centered 7-row window |
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING |
the whole partition |
Compute before you filter
Kevin wants December only, but December 1st’s average needs November 25 to 30. If you filtered the
daily CTE to December first, the window would have nothing before December 1st, and the first six
days would be averages of 1 to 6 days instead of 7. As with LAG, compute the window on the full data,
then filter in the outer query.
When you do report the first days of a series with no earlier data, they are averages of fewer than
seven days. Either drop them or count the rows in the frame
(COUNT(*) OVER (same window)) and show NULL when it is below 7.
ROWS counts rows, not days
6 PRECEDING means six rows back. If a day had zero orders, it has no row in daily, and the window
silently reaches back eight calendar days. Harbor & Pine has orders every day, so it is fine here. In
sparse data (one product, one city), generate a full calendar and LEFT JOIN onto it with
COALESCE(orders, 0) before applying the window.
Postgres 11+, DuckDB and MySQL 8 also allow a RANGE frame with an interval, which uses the values instead of row positions:
AVG(orders) OVER (
ORDER BY order_day
RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW
)
Postgres writes INTERVAL '6 days'. BigQuery only allows RANGE over numbers, so you order by
UNIX_DATE(order_day) and write RANGE BETWEEN 6 PRECEDING AND CURRENT ROW. This handles gaps in the dates, but a missing day still does not
count as zero in the average: it is just absent. The calendar join is the only way to average real
zeros.
Moving sum vs moving average
A 7-day moving sum (SUM(orders) OVER (... 6 PRECEDING ...)) answers “orders in the last 7 days”,
the basis of metrics like weekly active users computed daily. The frame is the same; only the function
changes.
Common mistakes
- Writing
RANGE 6 PRECEDINGon a date column without an interval, which errors or means something different by dialect. - Using
ROWS BETWEEN 7 PRECEDING AND CURRENT ROW, which is 8 days. - Filtering before the window, as above.
Lantern lit!
Practised: moving averagewindow framerows betweenavg over
- Solved
- No hints
- First run
