generate_series
PG 8.0+ (integer/numeric); PG 8.4+ (timestamp/timestamptz)→ setof same type as inputsGenerates a series of values from start to stop (inclusive) with the given step. Works with integers, numeric, and timestamps.
Signatures
generate_series ( start integer, stop integer [, step integer] ) → setof integergenerate_series ( start bigint, stop bigint [, step bigint] ) → setof bigintgenerate_series ( start numeric, stop numeric [, step numeric] ) → setof numericgenerate_series ( start timestamp, stop timestamp, step interval ) → setof timestampgenerate_series ( start timestamptz, stop timestamptz, step interval ) → setof timestamptzParameters
| Parameter | Type | Description |
|---|---|---|
| start | integer, numeric, or timestamp | Starting value (inclusive) |
| stop | same as start | Stopping value (inclusive) |
| step | same as start, or interval for timestamps | Increment (default 1). Negative step counts down. |
Examples
SELECT generate_series(1, 5);1, 2, 3, 4, 5SELECT generate_series(0, 10, 2);0, 2, 4, 6, 8, 10SELECT generate_series(now(), now() + interval '6 hours', interval '1 hour') AS hour;7 hourly timestampsSELECT date_trunc('day', generate_series('2025-01-01'::date, '2025-01-07'::date, '1 day'));7 days in JanuaryUse `generate_series` + LEFT JOIN to ensure all time slots appear in a report, even with zero counts. `WITH dates AS (SELECT generate_series(...)) SELECT d.ts, count(e.id) FROM dates d LEFT JOIN events e ON date_trunc('hour', e.ts) = d.ts GROUP BY d.ts`.
SELECT g.day, count(o.id) AS orders FROM generate_series('2025-01-01'::date, '2025-01-31'::date, '1 day') AS g(day) LEFT JOIN orders o ON o.created_at::date = g.day GROUP BY 1 ORDER BY 1;Daily order counts including days with zero orders