Postgres generate_series Builder
generate_series is the function that turns PostgreSQL into a row generator: it makes the calendar table your report needs, the empty time buckets a chart is missing, and the hundred thousand test rows you want before benchmarking an index. It also has edges people trip over - the stop value is included only when the step lands on it exactly, a month step is calendar arithmetic rather than 30 days, and a series generated as timestamp will not join cleanly against a date column. This builder previews the actual rows your parameters produce, including the row count and the real last value, then writes the SQL for a calendar table, fixed-width buckets with date_bin, a gap-filled LEFT JOIN report, period boundaries as half-open ranges, or an INSERT that fabricates test data. Everything is computed in your browser.
Do more than postgres generate_series builder — meet Chat2DB
Chat2DB is an AI-powered SQL client for Windows, macOS and Linux. Write SQL in natural language, format and optimize queries automatically, and manage MySQL, PostgreSQL, Oracle and 20+ other databases in one workspace.
How to use
- Pick a recipe, then set the start, stop and step. Steps accept any PostgreSQL interval text, such as 1 day, 15 minutes, 1 month or 1 day 6 hours.
- Check the preview: it shows how many rows the series returns, the first values, the real last value, and whether the stop bound is actually included.
- Copy the generated SQL, adjusting the table and column names for the join recipes, and run it against your database.
Frequently asked questions
Is the stop value of generate_series included?
Only if the step lands on it exactly. generate_series emits start, start + step, start + 2*step and so on for as long as the value is less than or equal to stop, then stops; it never overshoots. So generate_series(1, 10, 3) gives 1, 4, 7, 10 and includes the bound, while generate_series(1, 10, 4) gives 1, 5, 9 and stops short. The same rule applies to timestamps, which is why generating whole days from '2026-01-01' to '2026-12-31' produces 365 rows ending on December 31 rather than spilling into the next year. The preview above always shows the true last value so you can see which case you are in.
How do I show days with no data in a report?
Generate the days first and join the data onto them, rather than grouping the data and hoping every day appears. GROUP BY can only return buckets that contain at least one row, so a day with no orders simply vanishes and a chart draws a misleading straight line across the gap. The gap-filling recipe builds a spine with generate_series, LEFT JOINs the aggregate onto it, and wraps the measure in coalesce(..., 0). If a missing bucket means unchanged rather than zero - a balance, a stock level, a gauge - use the carry-forward variant that fills from the previous non-null value with a window function instead.
Why does adding 1 month repeatedly drift, and does generate_series have that problem?
Adding interval '1 month' to January 31 gives February 28, because PostgreSQL clamps to the end of the shorter month. If you then add another month to that result you get March 28, and the day of month has drifted permanently. generate_series avoids this because each value is computed as start + n * step from the original start rather than by adding to the previous value, so the series from January 31 gives February 28, March 31, April 30 and so on. For quarter and year boundaries, anchor the start with date_trunc so the whole series snaps to clean period starts. You can run and inspect all of these directly against your database with Chat2DB - download it at https://chat2db.ai/download or open https://app.chat2db.ai.
