Conception de la base de données
Pendant que vous pouvez travailler avec une date
séparée et time
colonnes, il n'y a vraiment aucun avantage sur un seul timestamp
colonne. J'adapterais :
ALTER TABLE tbl ADD column ts timestamp;
UPDATE tbl SET ts = date + time; -- assuming actual date and time types
ALTER TABLE tbl DROP column date, DROP column time;
Si la date et l'heure ne sont pas réelles date
et time
types de données, utilisez to_timestamp()
. Connexe :
- Calcul de la somme cumulée dans PostgreSQL
- Comment convertir "chaîne" en "horodatage sans fuseau horaire"
Requête
Ensuite, la requête est un peu plus simple :
SELECT *
FROM (
SELECT sn, generate_series(min(ts), max(ts), interval '5 min') AS ts
FROM tbl
WHERE sn = '4as11111111'
AND ts >= '2018-01-01'
AND ts < '2018-01-02'
GROUP BY 1
) grid
CROSS JOIN LATERAL (
SELECT round(avg(vin1), 2) AS vin1_av
, round(avg(vin2), 2) AS vin2_av
, round(avg(vin3), 2) AS vin3_av
FROM tbl
WHERE sn = grid.sn
AND ts >= grid.ts
AND ts < grid.ts + interval '5 min'
) avg;
db<>violon ici
Générer une grille d'heures de début dans la première sous-requête grid
, du premier au dernier qualificatif ligne dans le laps de temps donné.
Joindre aux lignes qui tombent dans chaque partition avec un LATERAL
joindre et agréger immédiatement les moyennes dans la sous-requête avg
. En raison des agrégats, il toujours renvoie une ligne même si aucune entrée n'est trouvée. Les moyennes par défaut sont NULL
dans ce cas.
Le résultat inclut tous les créneaux horaires entre la première et la dernière ligne de qualification dans le laps de temps donné. Diverses autres compositions de résultats auraient également du sens. Comme inclure tous créneaux horaires dans le laps de temps donné ou simplement des créneaux horaires avec des valeurs réelles. Dans la mesure du possible, je devais choisir une interprétation.
Index
Avoir au moins cet index multicolonne :
CRATE INDEX foo_idx ON tbl (sn, ts);
Ou sur (sn, ts, vin1, vin2, vin3)
pour autoriser les analyses d'index uniquement - si certaines conditions préalables sont remplies et en particulier si les lignes du tableau sont beaucoup plus larges que dans la démo.
Etroitement lié :
- LEFT JOIN lent sur CTE avec des intervalles de temps
- Meilleur moyen de compter les enregistrements par intervalles de temps arbitraires dans Rails+Postgres
Basé sur votre tableau d'origine
Comme demandé et clarifié dans le commentaire
, et plus tard mis à jour à nouveau dans la question pour inclure les colonnes mac
et loc
. Je suppose que vous voulez des moyennes séparées par (mac, loc)
.
date
et time
sont toujours des colonnes séparées, les colonnes vin* sont de type float
, et exclure les créneaux horaires sans lignes :
La requête mise à jour déplace également la fonction de retour d'ensemble generate_series()
au FROM
list, qui est plus propre avant Postgres 10 :
SELECT t.mac, sn.sn, t.loc, ts.ts::time AS time, ts.ts::date AS date
, t.vin1_av, t.vin2_av, t.vin3_av
FROM (SELECT text '4as11111111') sn(sn) -- provide sn here once
CROSS JOIN LATERAL (
SELECT min(date+time) AS min_ts, max(date+time) AS max_ts
FROM tbl
WHERE sn = sn.sn
AND date+time >= '2018-01-01 0:0' -- provide time frame here
AND date+time < '2018-01-02 0:0'
) grid
CROSS JOIN LATERAL generate_series(min_ts, max_ts, interval '5 min') ts(ts)
CROSS JOIN LATERAL (
SELECT mac, loc
, round(avg(vin1)::numeric, 2) AS vin1_av -- cast to numeric for round()
, round(avg(vin2)::numeric, 2) AS vin2_av -- but rounding is optional
, round(avg(vin3)::numeric, 2) AS vin3_av
FROM tbl
WHERE sn = sn.sn
AND date+time >= ts.ts
AND date+time < ts.ts + interval '5 min'
GROUP BY mac, loc
HAVING count(*) > 0 -- exclude empty slots
) t;
Créez un index d'expression multicolonne pour prendre en charge ceci :
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>violon ici
Mais je préférerais de loin utiliser timestamp
tout le long.