Exploring the RANGE() Function in DuckDB

The range() function is a handy utility in DuckDB that enables us to generate sequences of numbers or timestamps. We specify the starting point, the end point, and the step to use for each value in the range.

In this article, we’ll take a look at DuckDB’s range() function, along with some basic examples.

Syntax

The range() function can be used on numbers or timestamp values.

When used on numbers, it can be used in the following ways:

range(stop)
range(start, stop)
range(start, stop, step)

When used on timestamp values, the syntax goes like this:

range(start, stop, interval)

So we have a bit more flexibility when creating a range of numbers when compared to timestamps. Note that timestamps don’t necessarily have to be TIMESTAMP type. They can be DATE, TIMESTAMP, or TIMESTAMPTZ (timestamp with time zone) values. That said, passing DATE values will produce a range of TIMESTAMP values. Passing TIMESTAMPTZ values will produce TIMESTAMPTZ values.

Here’s a more detailed syntax:

range(BIGINT) -> BIGINT[]
range(BIGINT, BIGINT) -> BIGINT[]
range(BIGINT, BIGINT, BIGINT) -> BIGINT[]
range(TIMESTAMP, TIMESTAMP, INTERVAL) -> TIMESTAMP[]
range(TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH TIME ZONE, INTERVAL) -> TIMESTAMP WITH TIME ZONE[]

Basic Numeric Series

When we generate a range of numbers, we have the option of specifying the start and end point, or just the end point. We also have the option of specifying a step amount.

Let’s starting by specifying the end point:

SELECT range(5);

Result:

+-----------------+
| "range"(5) |
+-----------------+
| [0, 1, 2, 3, 4] |
+-----------------+

The endpoint or “stop” value is exclusive. By that I mean that it doesn’t include that value in the result. The example illustrates this point clearly. We provided a stop value of 5, and the range of numbers only went up to 4. This is in contrast to the generate_series() function, which does a similar thing to range() except that the stop value is inclusive. If we’d used generate_series() for this example, the series would’ve gone up to 5.

Specifying a Start Point

Here’s an example that uses a start point:

SELECT range(1, 5);

Result:

+---------------+
| "range"(1, 5) |
+---------------+
| [1, 2, 3, 4] |
+---------------+

The start value is inclusive, unlike the stop value. We specified 1 as the start value, and 1 is exactly where the range started.

Specifying a Step

We have the option of providing a third argument for the step value. Here’s an example where we generate even numbers from 0 to 10:

SELECT range(0, 10, 2);

Output:

+-------------------+
| "range"(0, 10, 2) |
+-------------------+
| [0, 2, 4, 6, 8] |
+-------------------+

Again, the stop value is exclusive, and so it only went up to 8.

Negative Steps

Negative steps work for descending sequences:

SELECT range(10, 0, -2);

Output:

+--------------------+
| "range"(10, 0, -2) |
+--------------------+
| [10, 8, 6, 4, 2] |
+--------------------+

Unnest the Results

We can use the unnest() function to return the values as separate rows in a table:

SELECT unnest(range(1,5));

Result:

+-----------------------+
| unnest("range"(1, 5)) |
+-----------------------+
| 1 |
| 2 |
| 3 |
| 4 |
+-----------------------+

Another way to do it is like this:

SELECT * FROM range(1,5);

Result:

+-------+
| range |
+-------+
| 1 |
| 2 |
| 3 |
| 4 |
+-------+

Generating a Date Series

We can also use the range() function to create timestamp sequences:

SELECT range(
    DATE '2031-10-01',
    DATE '2031-10-07',
    INTERVAL '1 day'
) AS result;

Result:

+--------------------------------------------------------------------------------------------------------------------------------+
| result |
+--------------------------------------------------------------------------------------------------------------------------------+
| [2031-10-01 00:00:00, 2031-10-02 00:00:00, 2031-10-03 00:00:00, 2031-10-04 00:00:00, 2031-10-05 00:00:00, 2031-10-06 00:00:00] |
+--------------------------------------------------------------------------------------------------------------------------------+

We can also unnest the result in order to have the timestamps returned in a table:

SELECT * FROM range(
    DATE '2031-10-01',
    DATE '2031-10-07',
    INTERVAL '1 day'
);

Result:

+---------------------+
| range |
+---------------------+
| 2031-10-01 00:00:00 |
| 2031-10-02 00:00:00 |
| 2031-10-03 00:00:00 |
| 2031-10-04 00:00:00 |
| 2031-10-05 00:00:00 |
| 2031-10-06 00:00:00 |
+---------------------+

This makes it a bit easier to read than with an array of timestamps.

We can add a third argument in order to specify an interval, such as weeks:

SELECT * FROM range(
    DATE '2031-01-06',
    DATE '2031-03-31',
    INTERVAL '1 week'
);

Result:

+---------------------+
| range |
+---------------------+
| 2031-01-06 00:00:00 |
| 2031-01-13 00:00:00 |
| 2031-01-20 00:00:00 |
| 2031-01-27 00:00:00 |
| 2031-02-03 00:00:00 |
| 2031-02-10 00:00:00 |
| 2031-02-17 00:00:00 |
| 2031-02-24 00:00:00 |
| 2031-03-03 00:00:00 |
| 2031-03-10 00:00:00 |
| 2031-03-17 00:00:00 |
| 2031-03-24 00:00:00 |
+---------------------+

Timestamp Series

The range() function returns its result as a TIMESTAMP, even when we pass DATE values (or as a TIMESTAMPTZ when passed TIMESTAMPTZ values). Let’s pass actual TIMESTAMP values to generate a time series across a single day:

SELECT * FROM range(
    TIMESTAMP '2031-12-20 09:00:00',
    TIMESTAMP '2031-12-20 18:00:00',
    INTERVAL '1 hour'
);

Output:

+---------------------+
| range |
+---------------------+
| 2031-12-20 09:00:00 |
| 2031-12-20 10:00:00 |
| 2031-12-20 11:00:00 |
| 2031-12-20 12:00:00 |
| 2031-12-20 13:00:00 |
| 2031-12-20 14:00:00 |
| 2031-12-20 15:00:00 |
| 2031-12-20 16:00:00 |
| 2031-12-20 17:00:00 |
+---------------------+

Timestamp With Time Zone

As mentioned, the range() function also supports TIMESTAMP WITH TIME ZONE values (also known as TIMESTAMPTZ):

SELECT * FROM range(
    TIMESTAMPTZ '2035-09-12 09:00:00+02:30',
    TIMESTAMPTZ '2035-09-12 18:00:00+02:30',
    INTERVAL '1 hour'
);

Here’s what that returns on my machine:

+------------------------+
| range |
+------------------------+
| 2035-09-12 16:30:00+10 |
| 2035-09-12 17:30:00+10 |
| 2035-09-12 18:30:00+10 |
| 2035-09-12 19:30:00+10 |
| 2035-09-12 20:30:00+10 |
| 2035-09-12 21:30:00+10 |
| 2035-09-12 22:30:00+10 |
| 2035-09-12 23:30:00+10 |
| 2035-09-13 00:30:00+10 |
+------------------------+