On this page

Use continuous aggregates

Create a continuous aggregate over a hypertable, refresh it, and read its typed summary columns. This example stores hourly statistics with TimescaleDB Toolkit.

Create the hourly summary

Start with a hypertable and a server with TimescaleDB and Toolkit installed. Add this migration after the readings table:

-- sgl.migration(no_transaction: true)
CREATE MATERIALIZED VIEW hourly_readings
WITH (timescaledb.continuous, timescaledb.materialized_only = true) AS
SELECT
    time_bucket(INTERVAL '1 hour', ts) AS bucket,
    buoy,
    count(*) AS samples,
    stats_agg(temperature) AS stats
FROM readings
GROUP BY bucket, buoy;

The view uses materialized_only = true, so it contains only refreshed data. Add the refresh and read queries:

-- sgl.query(RefreshHourly, exec)
CALL refresh_continuous_aggregate('hourly_readings', NULL, NULL);
-- sgl.query(HourlyStats, many)
SELECT bucket, buoy, samples, average(stats) AS mean
FROM hourly_readings
ORDER BY bucket, buoy;

Refresh outside a transaction

Run sgl generate, apply the migrations, and construct store.New(database). After writing readings, call the refresh on the connection rather than through RunInTx:

	if err := queries.RefreshHourly(ctx); err != nil {
		return fmt.Errorf("refreshing hourly_readings: %w", err)
	}
	fmt.Fprintln(output, "refreshing outside a transaction: done")

Then read the hourly rows:

	hours, err := queries.HourlyStats(ctx)
	if err != nil {
		return fmt.Errorf("reading the hourly summaries: %w", err)
	}
	fmt.Fprintf(output, "after the refresh, hourly_readings has %d rows:\n", len(hours))

Choose refresh ranges or a refresh policy that meet your application’s freshness requirements; generation infers the view’s columns but never refreshes its data.

Check the refresh and summaries

From the Seagull repository root, run the continuous-aggregate example:

cd examples/timescaledb/033_timescale_continuous_aggregates
go test .

Set SGL_EXAMPLE_DSN to a disposable database before running this server-backed test; without it, the test skips. See running examples for connection setup.

The complete example produces:

before the refresh, hourly_readings has 0 rows
refreshing in RunInTx: ERROR: refresh_continuous_aggregate() cannot run inside a transaction block (SQLSTATE 25001)
refreshing outside a transaction: done
after the refresh, hourly_readings has 6 rows:
  22:00  Doom Bar       readings 3  mean 14.20
  22:00  Stepper Point  readings 1  mean 15.00
  23:00  Doom Bar       readings 2  mean 13.70
  23:00  Stepper Point  readings 1  mean 14.60
  01:00  Doom Bar       readings 2  mean 13.20
  01:00  Stepper Point  readings 1  mean 14.00
the whole night, rolled up from the hourly summaries:
  Doom Bar       readings 7  mean 13.77  stddev 0.47  mean of hourly means 13.70
  Stepper Point  readings 3  mean 14.53  stddev 0.50  mean of hourly means 14.53

Keep refreshes outside transactions

The example applies migrations with migrate_backend_postgres.New(), so its view migration declares no_transaction: true. sgl migrate and migrate_backend_timescaledb.New() recognise this statement and run it outside a transaction without the directive. The server also refuses refresh_continuous_aggregate inside a transaction, as the example output demonstrates.

See the dialects/timescaledb reference for the complete contract. The example browser exposes other engine variants; use their actual SQL rather than changing only the dialect name.

Examples for this page