Table-Valued Functions
Table-valued functions (TVFs) are functions that return a set of rows rather than a single value. They are used in the FROM clause of a query, just like a regular table. Stoolap supports TVFs with full SQL integration: WHERE filtering, ORDER BY, LIMIT, JOINs, subqueries, CTEs, and aggregation all work seamlessly.
GENERATE_SERIES
Generates a series of values from start to stop (inclusive) with an optional step. Supports integer, float, and timestamp/date types.
Syntax
GENERATE_SERIES(start, stop)
GENERATE_SERIES(start, stop, step)
Parameters:
| Parameter | Description |
|---|---|
start |
The first value in the series |
stop |
The last value in the series (inclusive) |
step |
The increment between values (optional, auto-detected if omitted) |
Return value: A table with a single column named value.
Integer Series
Generate a sequence of integers.
-- Basic ascending series
SELECT * FROM generate_series(1, 5);
-- Returns: 1, 2, 3, 4, 5
-- With explicit step
SELECT * FROM generate_series(0, 10, 2);
-- Returns: 0, 2, 4, 6, 8, 10
-- Descending with negative step
SELECT * FROM generate_series(5, 1, -1);
-- Returns: 5, 4, 3, 2, 1
-- Auto-detect descending (no step needed)
SELECT * FROM generate_series(5, 1);
-- Returns: 5, 4, 3, 2, 1
-- Negative range
SELECT * FROM generate_series(-3, 3);
-- Returns: -3, -2, -1, 0, 1, 2, 3
-- Single value (start equals stop)
SELECT * FROM generate_series(3, 3);
-- Returns: 3
Float Series
When any argument is a float, the function generates floating-point values. Index-based generation is used internally to avoid floating-point drift.
-- Float series with fractional step
SELECT * FROM generate_series(0.0, 1.0, 0.5);
-- Returns: 0.0, 0.5, 1.0
-- Mixed integer and float arguments
SELECT * FROM generate_series(0, 2, 0.5);
-- Returns: 0.0, 0.5, 1.0, 1.5, 2.0
Timestamp/Date Series
Generate a sequence of timestamps or dates. The start and stop values can be date strings ('YYYY-MM-DD') or timestamp strings ('YYYY-MM-DD HH:MM:SS'). The step is an interval string.
Supported interval units:
| Unit | Examples |
|---|---|
| year/years | '1 year', '2 years' |
| month/months | '1 month', '3 months' |
| week/weeks | '1 week', '2 weeks' |
| day/days | '1 day', '7 days' |
| hour/hours | '1 hour', '6 hours' |
| minute/minutes/min | '1 minute', '30 min' |
| second/seconds/sec | '1 second', '15 sec' |
| millisecond/milliseconds/ms | '100 ms' |
| microsecond/microseconds/us | '500 us' |
-- Daily series
SELECT * FROM generate_series('2024-01-01', '2024-01-05', '1 day');
-- Returns: 2024-01-01, 2024-01-02, 2024-01-03, 2024-01-04, 2024-01-05
-- Auto-detect daily step
SELECT * FROM generate_series('2024-01-01', '2024-01-03');
-- Returns: 2024-01-01, 2024-01-02, 2024-01-03
-- Hourly series
SELECT * FROM generate_series(
'2024-01-01 00:00:00',
'2024-01-01 06:00:00',
'2 hours'
);
-- Returns: 00:00, 02:00, 04:00, 06:00
-- Every 10 minutes
SELECT * FROM generate_series(
'2024-01-01 00:00:00',
'2024-01-01 00:30:00',
'10 minutes'
);
-- Returns: 00:00, 00:10, 00:20, 00:30
-- Weekly series
SELECT * FROM generate_series('2024-01-01', '2024-01-29', '1 week');
-- Returns: Jan 1, Jan 8, Jan 15, Jan 22, Jan 29
-- Monthly series (1 month = 30 days)
SELECT * FROM generate_series('2024-01-01', '2024-04-01', '1 month');
-- Descending date series
SELECT * FROM generate_series('2024-01-05', '2024-01-01', '-1 day');
-- Returns: Jan 5, Jan 4, Jan 3, Jan 2, Jan 1
Column Aliases
The default output column is named value. You can rename it using standard SQL aliasing.
-- Table alias with column alias
SELECT n FROM generate_series(1, 5) AS gs(n);
-- Implicit alias (without AS keyword)
SELECT n FROM generate_series(1, 5) gs(n);
-- Using default column name
SELECT value FROM generate_series(1, 5);
Scalar Mode
When used in a SELECT expression (without FROM), GENERATE_SERIES returns a JSON array string. This matches DuckDB behavior.
SELECT generate_series(1, 5);
-- Returns: '[1, 2, 3, 4, 5]'
SELECT generate_series(0, 10, 2);
-- Returns: '[0, 2, 4, 6, 8, 10]'
SELECT generate_series('2024-01-01', '2024-01-03', '1 day');
-- Returns: '["2024-01-01T00:00:00+00:00", "2024-01-02T00:00:00+00:00", "2024-01-03T00:00:00+00:00"]'
Using with SQL Clauses
WHERE
SELECT * FROM generate_series(1, 10) AS g(value)
WHERE value > 7;
-- Returns: 8, 9, 10
SELECT * FROM generate_series('2024-01-01', '2024-01-10', '1 day') AS g(value)
WHERE value > '2024-01-07';
-- Returns: Jan 8, Jan 9, Jan 10
ORDER BY
SELECT * FROM generate_series(1, 5) AS g(value)
ORDER BY value DESC;
-- Returns: 5, 4, 3, 2, 1
LIMIT and OFFSET
SELECT * FROM generate_series(1, 100) AS g(value)
LIMIT 5;
-- Returns: 1, 2, 3, 4, 5
-- LIMIT with OFFSET using CTE
WITH gs AS (
SELECT * FROM generate_series(1, 10) AS g(value)
)
SELECT * FROM gs LIMIT 3 OFFSET 2;
-- Returns: 3, 4, 5
Aggregation
-- Sum of 1 to 100
SELECT SUM(value) FROM generate_series(1, 100) AS g(value);
-- Returns: 5050
-- Count
SELECT COUNT(*) FROM generate_series(1, 1000) AS g(value);
-- Returns: 1000
-- Count days in a year
SELECT COUNT(*) FROM generate_series('2024-01-01', '2024-12-31', '1 day') AS g(value);
-- Returns: 366 (2024 is a leap year)
JOINs
-- Join with a regular table
SELECT g.n, t.name
FROM generate_series(1, 3) AS g(n)
JOIN users t ON g.n = t.id
ORDER BY g.n;
-- Cross join two series (generates a grid)
SELECT a.value AS x, b.value AS y
FROM generate_series(1, 3) AS a(value)
CROSS JOIN generate_series(1, 2) AS b(value)
ORDER BY x, y;
-- Returns: (1,1), (1,2), (2,1), (2,2), (3,1), (3,2)
Subqueries and CTEs
-- In a subquery
SELECT * FROM (
SELECT * FROM generate_series(1, 5) AS g(n)
) sub
ORDER BY n;
-- In a CTE
WITH numbers AS (
SELECT value FROM generate_series(1, 10) AS g(value)
)
SELECT value, value * value AS squared
FROM numbers
WHERE value <= 5;
Practical Examples
Generate a Calendar Table
SELECT value AS date
FROM generate_series('2024-01-01', '2024-12-31', '1 day') AS g(value)
ORDER BY date;
Generate Hourly Time Slots
SELECT value AS slot
FROM generate_series(
'2024-01-01 08:00:00',
'2024-01-01 17:00:00',
'1 hour'
) AS g(value);
Fill Gaps in Time Series Data
-- Generate all dates, then left join with actual data
WITH dates AS (
SELECT value AS date
FROM generate_series('2024-01-01', '2024-01-31', '1 day') AS g(value)
)
SELECT d.date, COALESCE(o.total, 0) AS total
FROM dates d
LEFT JOIN daily_orders o ON d.date = o.order_date
ORDER BY d.date;
Number Table for Testing
-- Generate test IDs
INSERT INTO test_data (id, value)
SELECT value, value * 10
FROM generate_series(1, 100) AS g(value);
Multiplication Table
SELECT a.value AS x, b.value AS y, a.value * b.value AS product
FROM generate_series(1, 10) AS a(value)
CROSS JOIN generate_series(1, 10) AS b(value)
ORDER BY x, y;
Behavior Notes
- Inclusive bounds: Both
startandstopare included in the output when the step aligns. - Auto-detect direction: When
stepis omitted, the function automatically determines the direction. For integers and floats, it uses+1or-1. For timestamps, it uses+1 dayor-1 day. - Direction mismatch: If the step goes in the opposite direction from start to stop (e.g.,
generate_series(1, 5, -1)), an empty result is returned. This follows PostgreSQL behavior. - Zero step: A step of zero is an error.
- Safety limit: A maximum of 10,000,000 rows can be generated per call to prevent out-of-memory conditions.
- Case insensitive:
GENERATE_SERIES,generate_series, andGenerate_Seriesall work. - Month approximation: The
monthinterval unit is approximated as 30 days. For exact calendar month arithmetic, use application-level logic.