N051 Tier 4 · Advanced

generate_series() for Sequences and Date Spines in SQL

generate_series() is a PostgreSQL set-returning function that produces a sequence of values from a start point to an end point at a specified step interval. Its primary analytical use is generating a complete, gap-free sequence of dates or integers that can be joined to fact data to ensure every period appears in the result, even periods with no underlying activity.

generate_series() produces a sequence of values as rows — dates, timestamps, or integers — that you can query like a table. Its primary use in analytics is building a complete list of every date in a range, so that gaps in your data show up as zeros instead of disappearing from your results.

Here's the problem it solves. Your orders table only has rows for days when orders happened. Group by day and you get a result with no row for quiet days — they simply don't exist in the data. If you need those days to appear with a count of 0, there's nothing to GROUP BY. You need to create those rows synthetically.

generate_series() creates them. It takes a start, an end, and a step:

SELECT generate_series(
    '2024-01-01'::date,
    '2024-12-31'::date,
    '1 day'::interval
) AS day

This produces 366 rows, one per calendar day in 2024. The function expands into rows in FROM like a table. From here, the standard pattern is to wrap it in a CTE and LEFT JOIN to your fact data:

WITH date_spine AS (
    SELECT generate_series(
        '2024-01-01'::date,
        '2024-12-31'::date,
        '1 day'::interval
    )::date AS day
)
SELECT
    ds.day,
    COALESCE(COUNT(o.id), 0) AS order_count
FROM date_spine ds
LEFT JOIN orders o ON o.ordered_at::date = ds.day
GROUP BY ds.day
ORDER BY ds.day

The LEFT JOIN keeps every generated day regardless of whether orders exist. Days with no orders produce NULL in o.id, COUNT ignores NULLs, and COALESCE converts the resulting NULL count to 0. Every day in the range appears in the output.

Here's a monthly version you can run on the ecommerce data:

The one thing that trips people up

When you pass date inputs with an interval step, PostgreSQL returns timestamps, not dates. The ::date cast inside the CTE converts each value back to a plain date. Without it, the join on o.order_date (a date column) may fail or silently produce no matches because the types don't align.

The step controls the resolution. Change '1 day' to '1 week' or '1 month' for weekly or monthly spines. For monthly sequences, PostgreSQL applies calendar arithmetic: adding one month to January 31 gives February 28 (or 29 in a leap year), not March 2. The arithmetic follows real calendar logic, so month-end dates compress forward to the last day of the next month.

generate_series() works with integers too: generate_series(1, 100, 1) produces 100 rows numbered 1 through 100. The date spine pattern is the most common analytical use, but integer sequences appear in test data generation and row-numbering problems as well.

generate_series() in FROM vs in SELECT

You can call generate_series() directly in the FROM clause without wrapping it in a CTE, but the CTE approach is cleaner and gives the sequence a named column. Calling it in the SELECT list is also valid for simple cases: SELECT generate_series(1, 5) returns 5 rows with values 1 through 5. When used in FROM alongside other tables, always wrap it in a CTE or subquery with an explicit column alias — the raw function call produces a column named generate_series, which is awkward to join on.

Practice

10 generate_series() for Sequences and Date Spines practice problems

Write a query to return a sequence of integers from `1` through `5`, with each value appearing as a separate row.

easy ecommerce

Write a query to return the first day of each calendar month from January through June `2024`, one date per row.

easy ecommerce

Write a query to return a sequence covering every day from `'2024-01-08'` through `'2024-01-14'`, one date per row.

easy ecommerce

Write a query to return every date from `'2024-01-01'` through `'2024-01-07'` alongside the number of orders placed on that date. Days with no orders should appear with an order count of `0`.

medium ecommerce

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.

medium ecommerce

Write a query to return every date from `'2024-01-01'` through `'2024-01-07'` alongside the number of events recorded that day. Days with no events should appear with a count of `0`.

medium analytics

Write a query to return every integer from `1` through `10` alongside the customer name if a `customers` record with that `id` is on file, or a missing value if no record was found.

medium ecommerce

Write a query to return the total number of rows produced when `generate_series` is called with a start of `'2024-03-01'`, an end of `'2024-02-01'`, and a step of `1` day.

hard ecommerce

Write a query to return all renewal dates produced by `generate_series` with a start of `'2024-01-31'`, an end of `'2024-04-30'`, and a step of `'1 month'`. Each result should be cast back to `DATE`.

hard ecommerce

Write a query to return every date from `'2024-01-01'` through `'2024-01-14'` for which no order is on record.

hard ecommerce

These problems are part of the generate_series() for Sequences and Date Spines lesson in SQLMaxx, with instant grading and a worked solution on each.

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.