N044-M1 Tier 4 · Advanced · medium ecommerce · Brightlane

Return every order's ID, ordered-at timestamp, total amount, and the combined `total_amount` across every order whose `ordered_at` falls within the 30 calendar days ending on and including that order's `ordered_at`

Part of Window Frames (ROWS, RANGE, GROUPS) in SQL

The problem

Brightlane's finance team monitors revenue velocity using a 30-day rolling total — every order paired with the combined revenue of every other order placed in the trailing 30-calendar-day window.

Write a query to return every order's ID, ordered-at timestamp, total amount, and the combined total_amount across every order whose ordered_at falls within the 30 calendar days ending on and including that order's ordered_at.

Assumptions:

  • The rolling 30-day window covers every order whose ordered_at is within 29 calendar days before the current row's ordered_at, plus the current order itself.
  • The window is calendar-based rather than position-based: gaps in the date sequence reduce the order count in the window; clusters of orders on the same day all contribute together.
  • The final result is sorted by ordered_at ascending.

Output:

  • One row per order, with columns id, ordered_at, total_amount, and rolling_30d_sum. Sorted by ordered_at.
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
SELECT
  id,
  ordered_at,
  total_amount,
  SUM(total_amount) OVER (
    ORDER BY
      ordered_at RANGE BETWEEN INTERVAL '29 days' PRECEDING
      AND CURRENT ROW
  ) AS rolling_30d_sum
FROM
  orders
ORDER BY
  ordered_at

The shape

RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW makes the frame value-based instead of position-based. The window covers every order whose ordered_at falls inside the 30 calendar days ending on the current row's date, regardless of how many orders that range contains.

Clause by clause

  • SELECT id, ordered_at, total_amount, SUM(total_amount) OVER (ORDER BY ordered_at RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW) AS rolling_30d_sum returns each order with the calendar-window sum attached. The window has no PARTITION BY, so it spans every order; ORDER BY ordered_at is the value the frame measures distance against; the RANGE frame says "every row whose ordering value is within 29 days before mine, plus mine."
  • FROM orders reads every order.
  • ORDER BY ordered_at sorts the result chronologically for display.

Why RANGE and not ROWS

ROWS BETWEEN 29 PRECEDING AND CURRENT ROW would sum the current order plus the 29 physically prior orders, regardless of their dates. If orders cluster on certain days and gaps appear on others, those 30 rows could span 60 days or 10 days depending on the data. The finance team wants a 30-calendar-day metric, so the frame has to be measured in days, not in row positions.

The trap

RANGE requires that the ORDER BY expression and the frame offset be type-compatible. ordered_at is a timestamp, and the offset is an INTERVAL, which matches. Try the same frame with an integer offset against a timestamp ordering and PostgreSQL raises an error. The interval-against-timestamp pairing is what makes calendar-window frames work; the pairing is load-bearing.

You practiced RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW — a value-based frame measured in calendar days; the window's record count varies with how many records fall into the calendar range.

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.