CREATE EXTENSION IF NOT EXISTS timescaledb; CREATE TABLE IF NOT EXISTS edge ( id text PRIMARY KEY, name text NOT NULL, parent_id text REFERENCES edge(id) ON DELETE RESTRICT, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS tag ( id text PRIMARY KEY, name text NOT NULL, tag_group text, min_value double precision, max_value double precision, unit_of_measurement text, comment text, precision smallint, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS edge_tag ( edge_id text NOT NULL REFERENCES edge(id) ON DELETE CASCADE, tag_id text NOT NULL REFERENCES tag(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (edge_id, tag_id) ); CREATE TABLE IF NOT EXISTS history_points ( time timestamptz NOT NULL, edge_id text NOT NULL, tag_id text NOT NULL, value double precision NOT NULL, received_at timestamptz NOT NULL DEFAULT now() ); SELECT create_hypertable( 'history_points', 'time', chunk_time_interval => INTERVAL '1 day', if_not_exists => TRUE ); CREATE INDEX IF NOT EXISTS history_points_edge_tag_time_desc_idx ON history_points (edge_id, tag_id, time DESC) INCLUDE (value); CREATE INDEX IF NOT EXISTS history_points_time_brin_idx ON history_points USING brin (time); CREATE TABLE IF NOT EXISTS current_values ( edge_id text NOT NULL, tag_id text NOT NULL, value double precision NOT NULL, time timestamptz NOT NULL, updated_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (edge_id, tag_id) ); CREATE INDEX IF NOT EXISTS current_values_updated_at_idx ON current_values (updated_at DESC); ALTER TABLE history_points SET ( timescaledb.compress, timescaledb.compress_segmentby = 'edge_id,tag_id', timescaledb.compress_orderby = 'time DESC' ); SELECT add_compression_policy( 'history_points', INTERVAL '7 days', if_not_exists => TRUE ); CREATE MATERIALIZED VIEW IF NOT EXISTS history_1m WITH (timescaledb.continuous) AS SELECT time_bucket(INTERVAL '1 minute', time) AS bucket, edge_id, tag_id, first(value, time) AS first_value, min(value) AS min_value, max(value) AS max_value, avg(value) AS avg_value, last(value, time) AS last_value, count(*)::integer AS point_count FROM history_points GROUP BY bucket, edge_id, tag_id WITH NO DATA; CREATE MATERIALIZED VIEW IF NOT EXISTS history_5m WITH (timescaledb.continuous) AS SELECT time_bucket(INTERVAL '5 minutes', time) AS bucket, edge_id, tag_id, first(value, time) AS first_value, min(value) AS min_value, max(value) AS max_value, avg(value) AS avg_value, last(value, time) AS last_value, count(*)::integer AS point_count FROM history_points GROUP BY bucket, edge_id, tag_id WITH NO DATA; CREATE MATERIALIZED VIEW IF NOT EXISTS history_1h WITH (timescaledb.continuous) AS SELECT time_bucket(INTERVAL '1 hour', time) AS bucket, edge_id, tag_id, first(value, time) AS first_value, min(value) AS min_value, max(value) AS max_value, avg(value) AS avg_value, last(value, time) AS last_value, count(*)::integer AS point_count FROM history_points GROUP BY bucket, edge_id, tag_id WITH NO DATA; CREATE MATERIALIZED VIEW IF NOT EXISTS history_1d WITH (timescaledb.continuous) AS SELECT time_bucket(INTERVAL '1 day', time) AS bucket, edge_id, tag_id, first(value, time) AS first_value, min(value) AS min_value, max(value) AS max_value, avg(value) AS avg_value, last(value, time) AS last_value, count(*)::integer AS point_count FROM history_points GROUP BY bucket, edge_id, tag_id WITH NO DATA; CREATE INDEX IF NOT EXISTS history_1m_edge_tag_bucket_idx ON history_1m (edge_id, tag_id, bucket DESC); CREATE INDEX IF NOT EXISTS history_5m_edge_tag_bucket_idx ON history_5m (edge_id, tag_id, bucket DESC); CREATE INDEX IF NOT EXISTS history_1h_edge_tag_bucket_idx ON history_1h (edge_id, tag_id, bucket DESC); CREATE INDEX IF NOT EXISTS history_1d_edge_tag_bucket_idx ON history_1d (edge_id, tag_id, bucket DESC); DO $$ BEGIN PERFORM add_continuous_aggregate_policy( 'history_1m', start_offset => INTERVAL '7 days', end_offset => INTERVAL '1 minute', schedule_interval => INTERVAL '1 minute' ); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN PERFORM add_continuous_aggregate_policy( 'history_5m', start_offset => INTERVAL '30 days', end_offset => INTERVAL '5 minutes', schedule_interval => INTERVAL '5 minutes' ); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN PERFORM add_continuous_aggregate_policy( 'history_1h', start_offset => INTERVAL '180 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes' ); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN PERFORM add_continuous_aggregate_policy( 'history_1d', start_offset => INTERVAL '5 years', end_offset => INTERVAL '1 day', schedule_interval => INTERVAL '1 hour' ); EXCEPTION WHEN duplicate_object THEN NULL; END $$;