Time and continuous aggregates
Learn to work with timezones and continuous aggregates
Functions that depend on a local timezone setting inside a continuous aggregate are not supported. You cannot adjust to a local time because the timezone setting changes from user to user.
To manage this, you can use explicit timezones in the view definition. Alternatively, you can create your own custom aggregation scheme for tables that use an integer time column.
Declare an explicit timezone
Section titled “Declare an explicit timezone”The most common method of working with timezones is to declare an explicit timezone in the view query.
- Create the view with an explicit timezoneCREATE MATERIALIZED VIEW device_summaryWITH (timescaledb.continuous)ASSELECTtime_bucket('1 hour', observation_time) AS bucket,min(observation_time AT TIME ZONE 'EST') AS min_time,device_id,avg(metric) AS metric_avg,max(metric) - min(metric) AS metric_spreadFROMdevice_readingsGROUP BY bucket, device_id;
- Cast to a timestamp after the view
Alternatively, you can cast to a timestamp using
SELECT:SELECT min_time::timestamp FROM device_summary;
Integer-based time
Section titled “Integer-based time”Date and time is usually expressed as year-month-day and hours:minutes:seconds. Most TimescaleDB databases use a PostgreSQL date/time-type column to express the date and time. However, in some cases, you might need to convert these common time and date formats to a format that uses an integer. The most common integer time is Unix epoch time, which is the number of seconds since the Unix epoch of 1970-01-01, but other types of integer-based time formats are possible.
These examples use a hypertable called devices that contains CPU and disk
usage information. The devices measure time using the Unix epoch.
To create a hypertable that uses an integer-based column as time, you need to provide the chunk time interval. In this case, each chunk is 10 minutes.
Define the integer-based time column and chunk time interval:
CREATE TABLE devices( time BIGINT, -- Time in minutes since epoch cpu_usage INTEGER, -- Total CPU usage disk_usage INTEGER, -- Total disk usage PRIMARY KEY (time)) WITH ( tsdb.hypertable, tsdb.partition_column='time', tsdb.chunk_interval='10');For TimescaleDB v2.23.0 and higher, the table is automatically partitioned on the first column
in the table with a timestamp data type. If multiple columns are suitable candidates as a partitioning column,
TimescaleDB throws an error and asks for an explicit definition. For earlier versions, set partition_column to a
time column.
If you are self-hosting TimescaleDB v2.20.0 to v2.22.1, to convert your data to the columnstore after a specific time interval, you have to call add_columnstore_policy after you call CREATE TABLE
If you are self-hosting TimescaleDB v2.19.3 and below, create a PostgreSQL relational table, then convert it using create_hypertable. You then enable hypercore with a call to ALTER TABLE.
To define a continuous aggregate on a hypertable that uses integer-based time,
you need to have a function to get the current time in the correct format, and
set it for the hypertable. You can do this with the
set_integer_now_func
function. It can be defined as a regular PostgreSQL function, but needs to be
STABLE,
take no arguments, and return an integer value of the same type as the time
column in the table. When you have set up the time-handling, you can create the
continuous aggregate.
- Set up the time conversion functionCREATE FUNCTION current_epoch() RETURNS BIGINTLANGUAGE SQL STABLE AS $$SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)::bigint;$$;SELECT set_integer_now_func('devices', 'current_epoch');
- Create the continuous aggregate for the devices tableCREATE MATERIALIZED VIEW devices_summaryWITH (timescaledb.continuous) ASSELECT time_bucket('500', time) AS bucket,avg(cpu_usage) AS avg_cpu,avg(disk_usage) AS avg_diskFROM devicesGROUP BY bucket;
- Insert some rowsCREATE EXTENSION tablefunc;INSERT INTO devices(time, cpu_usage, disk_usage)SELECT time,normal_rand(1,70,10) AS cpu_usage,normal_rand(1,2,1) * (row_number() over()) AS disk_usageFROM generate_series(1,10000) AS time;
This command uses the
tablefuncextension to generate a normal distribution, and uses therow_numberfunction to turn it into a cumulative sequence. - Check the view datapostgres=# SELECT * FROM devices_summary ORDER BY bucket LIMIT 10;bucket | avg_cpu | avg_disk--------+---------------------+----------------------0 | 63.0000000000000000 | 6.00000000000000005 | 69.8000000000000000 | 9.600000000000000010 | 70.8000000000000000 | 24.000000000000000015 | 75.8000000000000000 | 37.600000000000000020 | 71.6000000000000000 | 26.800000000000000025 | 67.6000000000000000 | 56.000000000000000030 | 68.8000000000000000 | 90.200000000000000035 | 71.6000000000000000 | 88.800000000000000040 | 66.4000000000000000 | 81.200000000000000045 | 68.2000000000000000 | 106.0000000000000000(10 rows)
Learn more
Section titled “Learn more”- Understand continuous aggregates: How continuous aggregates work, JOINs, and function support.
- Create a continuous aggregate: Step-by-step guide to creating and querying a continuous aggregate.
- Refresh continuous aggregates: Set up automatic refresh policies.
- Understand time buckets: How time bucket origins and timezones work.
CREATE MATERIALIZED VIEWreference: Full API reference.