PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment obtenir des valeurs moyennes pour des intervalles de temps dans Postgres

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 :

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é :

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.