Files
energy-collector/schema.sql

141 lines
5.6 KiB
SQL

-- Run once as the TimescaleDB superuser (fitdata):
-- psql -h localhost -p 5433 -U fitdata -f schema.sql
CREATE DATABASE energy;
\c energy
CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE USER energy WITH PASSWORD 'changeme';
GRANT ALL ON DATABASE energy TO energy;
GRANT ALL ON SCHEMA public TO energy;
-- ── Raw hypertables ───────────────────────────────────────────────────────────
-- Written by energy-collector (pvcollect replacement) every 10 s
CREATE TABLE inverter (
time TIMESTAMPTZ NOT NULL,
pv1_power REAL, -- W
pv2_power REAL, -- W
pv_l1_power REAL, -- W (inverter AC output phase 1)
pv_l2_power REAL, -- W
pv_l3_power REAL, -- W
battery_soc REAL, -- %
grid_import_kwh REAL, -- kWh cumulative
grid_export_kwh REAL, -- kWh cumulative
pv_energy_kwh REAL -- kWh cumulative
);
SELECT create_hypertable('inverter', 'time', chunk_time_interval => INTERVAL '7 days');
-- Written by energy-collector (meter replacement) every 10 s, one row per device
CREATE TABLE power_meter (
time TIMESTAMPTZ NOT NULL,
device TEXT NOT NULL, -- 'house' | 'barn'
l1_power REAL, -- W
l2_power REAL, -- W
l3_power REAL, -- W
import_kwh REAL, -- kWh cumulative
export_kwh REAL -- kWh cumulative
);
SELECT create_hypertable('power_meter', 'time', chunk_time_interval => INTERVAL '7 days');
CREATE INDEX ON power_meter (device, time DESC);
GRANT ALL ON ALL TABLES IN SCHEMA public TO energy;
-- ── Continuous aggregates ─────────────────────────────────────────────────────
-- 10-minute buckets — live chart (1 h, 6 h, 24 h ranges)
CREATE MATERIALIZED VIEW inverter_10m
WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS
SELECT time_bucket('10 minutes', time) AS bucket,
AVG(pv1_power + pv2_power) AS pv_power,
AVG(battery_soc) AS battery_soc
FROM inverter
GROUP BY bucket;
CREATE MATERIALIZED VIEW power_meter_10m
WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS
SELECT time_bucket('10 minutes', time) AS bucket,
device,
AVG(l1_power + l2_power + l3_power) AS total_power
FROM power_meter
GROUP BY bucket, device;
-- 1-hour buckets — live chart (7 d range), built on 10 m (hierarchical)
CREATE MATERIALIZED VIEW inverter_1h
WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS
SELECT time_bucket('1 hour', bucket) AS bucket,
AVG(pv_power) AS pv_power,
AVG(battery_soc) AS battery_soc
FROM inverter_10m
GROUP BY 1;
CREATE MATERIALIZED VIEW power_meter_1h
WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS
SELECT time_bucket('1 hour', bucket) AS bucket,
device,
AVG(total_power) AS total_power
FROM power_meter_10m
GROUP BY 1, device;
-- Daily last-value snapshots — basis for bar-chart energy deltas
-- Query with LAG() to get per-period consumption.
CREATE MATERIALIZED VIEW inverter_daily
WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS
SELECT time_bucket('1 day', time) AS bucket,
last(grid_import_kwh, time) AS grid_import_kwh,
last(grid_export_kwh, time) AS grid_export_kwh,
last(pv_energy_kwh, time) AS pv_energy_kwh
FROM inverter
GROUP BY bucket;
CREATE MATERIALIZED VIEW power_meter_daily
WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS
SELECT time_bucket('1 day', time) AS bucket,
device,
last(import_kwh, time) AS import_kwh
FROM power_meter
GROUP BY bucket, device;
-- ── Retention — keep 30 days of raw data; aggregates stay forever ─────────────
SELECT add_retention_policy('inverter', INTERVAL '30 days');
SELECT add_retention_policy('power_meter', INTERVAL '30 days');
-- ── Refresh policies ──────────────────────────────────────────────────────────
SELECT add_continuous_aggregate_policy('inverter_10m',
start_offset => INTERVAL '1 hour',
end_offset => INTERVAL '10 minutes',
schedule_interval => INTERVAL '10 minutes');
SELECT add_continuous_aggregate_policy('power_meter_10m',
start_offset => INTERVAL '1 hour',
end_offset => INTERVAL '10 minutes',
schedule_interval => INTERVAL '10 minutes');
SELECT add_continuous_aggregate_policy('inverter_1h',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '1 hour');
SELECT add_continuous_aggregate_policy('power_meter_1h',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '1 hour');
SELECT add_continuous_aggregate_policy('inverter_daily',
start_offset => INTERVAL '3 days',
end_offset => INTERVAL '1 day',
schedule_interval => INTERVAL '1 day');
SELECT add_continuous_aggregate_policy('power_meter_daily',
start_offset => INTERVAL '3 days',
end_offset => INTERVAL '1 day',
schedule_interval => INTERVAL '1 day');
-- Grant SELECT on all tables and views (including continuous aggregates) to energy.
-- Run after all views are created so the grant covers them.
GRANT SELECT ON ALL TABLES IN SCHEMA public TO energy;