N051-M2 Tier 4 · Advanced · medium ecommerce · Brightlane

Return every date in that five-day range and its total order revenue. Days with no orders should appear with a missing revenue value

Part of generate_series() for Sequences and Date Spines in SQL

The problem

Brightlane's finance team needs total order revenue for each day from '2024-01-15' through '2024-01-19'. Every date in that range must appear, including days with no orders.

Write a query to return every date in that five-day range and its total order revenue. Days with no orders should appear with a missing revenue value.

Assumptions:

  • The orders table has one row per order with an ordered_at timestamp and a total_amount.
  • The five dates form a contiguous calendar sequence; every date in the range must appear in the result.
  • For each date, the daily revenue is the combined total_amount across orders whose ordered_at::date equals that date. Days with no orders show a missing revenue value (not 0).

Output:

  • One row per date in the five-day range, with columns day and daily_revenue.
Schema · ecommerce 5 tables
categories
id integer
name text
parent_id? integer
products
id integer
name text
category_id integer
price numeric
stock_qty integer
attributes? jsonb
order_items
id integer
order_id integer
product_id integer
quantity integer
unit_price numeric
customers
id integer
name text
email text
city? text
country text
created_at timestamptz
is_active boolean
orders
id integer
customer_id integer
ordered_at timestamptz
status text
total_amount numeric

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution Try it yourself first
Solution query
WITH
  spine AS (
    SELECT
      GENERATE_SERIES('2024-01-15'::date, '2024-01-19'::date, INTERVAL '1 day')::date AS DAY
  )
SELECT
  s.day,
  SUM(o.total_amount) AS daily_revenue
FROM
  spine s
  LEFT JOIN orders o ON o.ordered_at::date = s.day
GROUP BY
  s.day

The shape

The spine CTE emits each of the five dates as its own row, and the LEFT JOIN keeps every spine day in the result regardless of whether orders match. SUM(o.total_amount) over zero matching rows returns NULL, which is the missing-revenue display the finance team asked for.

Clause by clause

  • WITH spine AS (SELECT generate_series('2024-01-15'::date, '2024-01-19'::date, interval '1 day')::date AS day) builds the five-day spine and casts each generated value back to date so it matches the cast applied to ordered_at in the join.
  • SELECT s.day, SUM(o.total_amount) AS daily_revenue returns the date and the summed order revenue. With no matching orders, SUM is taken over an empty set of rows, which returns NULL by definition.
  • FROM spine s LEFT JOIN orders o ON o.ordered_at::date = s.day pairs each generated day with the orders placed that day. Days with no orders still appear in the join result, with NULL in every o.* column.
  • GROUP BY s.day produces one row per date in the spine.

Why this and not COALESCE(SUM(...), 0)

The prompt asks for a missing value on quiet days, not a zero, so the bare SUM is correct. If the requirement had been "show 0 for quiet days," wrapping the aggregate as COALESCE(SUM(o.total_amount), 0) would convert the NULL to a zero. The two outputs look almost identical but mean different things: NULL says no revenue data existed for that day; 0 says the data existed and summed to zero. On a date spine, only NULL communicates "no activity" honestly.

The trap

SUM and COUNT behave differently on empty groups. COUNT returns 0 over zero rows; SUM returns NULL. A query author who solved the zero-fill problem by relying on COUNT returning 0 will see NULLs reappear the moment the aggregate switches to SUM. The fix depends on what the prompt asks for: leave the NULL, or wrap it in COALESCE.

You practiced the date-spine pattern with SUM instead of COUNT — the empty-day distinction matters: COUNT returns 0 over zero records, SUM returns missing.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.