Time 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

The most common method of working with timezones is to declare an explicit timezone in the view query.

Procedure: Declaring an explicit timezone

  1. At the psqlprompt, create the view and declare the timezone:

    1. CREATE MATERIALIZED VIEW device_summary
    2. WITH (timescaledb.continuous)
    3. AS
    4. SELECT
    5. time_bucket('1 hour', observation_time) AS bucket,
    6. min(observation_time AT TIME ZONE 'EST') AS min_time,
    7. device_id,
    8. avg(metric) AS metric_avg,
    9. max(metric) - min(metric) AS metric_spread
    10. FROM
    11. device_readings
    12. GROUP BY bucket, device_id;
  2. Alternatively, you can cast to a timestamp after the view using SELECT:

    1. SELECT min_time::timestamp FROM device_summary;

Integer-based time

Date and time is usually expressed as year-month-day and hours:minutes:seconds. Most TimescaleDB databases use a 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 of these is Unix time, which is the number of seconds since the Unix epoch (1970-01-01), but other types of integer-based time formats are possible.

In these examples, we have a hypertable called devices that contains CPU and disk usage for devices. These devices measure time using microfortnights since epoch, under the humorous but impractical system of measurement called the furlong-firkin-fortnight (FFF) system.

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 consists of a millifortnight, which is equivalent to 1000 microfortnights, or about twenty minutes.

Procedure: Creating a table with a custom integer-based time column

  1. At the psql prompt, create a table and define the integer-based time column:

    1. CREATE TABLE devices(
    2. time BIGINT, -- Time in microfortnights since epoch
    3. cpu_usage INTEGER, -- Total CPU usage
    4. disk_usage INTEGER, -- Total disk usage
    5. PRIMARY KEY (time)
    6. );
  2. Define the chunk time interval:

    1. SELECT create_hypertable('devices', 'time',
    2. chunk_time_interval => 1000);

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. This is done using the set_integer_now_func. 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.

Procedure: Creating a continuous aggregate with integer-based time

  1. At the psql prompt, set up a function to convert the time to the FFF system:

    1. CREATE FUNCTION current_microfortnight() RETURNS BIGINT
    2. LANGUAGE SQL STABLE AS $$
    3. SELECT CAST(1209600 * EXTRACT(EPOCH FROM CURRENT_TIME) / 1000000 AS BIGINT)
    4. $$;
    5. SELECT set_integer_now_func('devices', 'current_microfortnight');
  2. Create the continuous aggregate for the devices table:

    1. CREATE MATERIALIZED VIEW devices_summary
    2. WITH (timescaledb.continuous) AS
    3. SELECT time_bucket('500', time) AS bucket,
    4. avg(cpu_usage) AS avg_cpu,
    5. avg(disk_usage) AS avg_disk
    6. FROM devices
    7. GROUP BY bucket;
  3. Insert some rows into the table:

    1. CREATE EXTENSION tablefunc;
    2. INSERT INTO devices(time, cpu_usage, disk_usage)
    3. SELECT time,
    4. normal_rand(1,70,10) AS cpu_usage,
    5. normal_rand(1,2,1) * (row_number() over()) AS disk_usage
    6. FROM generate_series(1,10000) AS time;

    This command uses the tablefunc extension to generate a normal distribution, and uses the row_number function to turn it into a cumulative sequence.

  4. Check that the view contains the correct data:

    1. postgres=# SELECT * FROM devices_summary ORDER BY bucket LIMIT 10;
    2. bucket | avg_cpu | avg_disk
    3. --------+---------------------+----------------------
    4. 0 | 63.0000000000000000 | 6.0000000000000000
    5. 5 | 69.8000000000000000 | 9.6000000000000000
    6. 10 | 70.8000000000000000 | 24.0000000000000000
    7. 15 | 75.8000000000000000 | 37.6000000000000000
    8. 20 | 71.6000000000000000 | 26.8000000000000000
    9. 25 | 67.6000000000000000 | 56.0000000000000000
    10. 30 | 68.8000000000000000 | 90.2000000000000000
    11. 35 | 71.6000000000000000 | 88.8000000000000000
    12. 40 | 66.4000000000000000 | 81.2000000000000000
    13. 45 | 68.2000000000000000 | 106.0000000000000000
    14. (10 rows)