sql >> Database teknologi >  >> RDS >> PostgreSQL

Sådan får du gennemsnitsværdier for tidsintervaller i Postgres

DB-design

Mens du kan arbejde med separat dato og tid kolonner, er der virkelig ingen fordel i forhold til et enkelt tidsstempel kolonne. Jeg ville tilpasse:

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;

Hvis dato og klokkeslæt ikke er faktisk dato og tid datatyper, brug to_timestamp() . Relateret:

Forespørgsel

Så er forespørgslen lidt enklere:

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<>fiddle her

Generer et gitter af starttider i den første underforespørgsel gitter , der løber fra den første til den sidste kvalificerende række i den givne tidsramme.

Slut dig til rækker, der falder i hver partition med en LATERAL join og aggreger straks gennemsnit i underforespørgslen avg . På grund af aggregaterne er det altid returnerer en række, selvom der ikke findes nogen poster. Gennemsnit er som standard NULL i dette tilfælde.

Resultatet inkluderer alle tidsintervaller mellem første og sidste kvalificerende række i den givne tidsramme. Forskellige andre resultatsammensætninger ville også give mening. Gerne inklusive alle timeslots i den givne tidsramme eller blot tidsslots med faktiske værdier. Alt muligt, jeg var nødt til at vælge én fortolkning.

Indeks

Hav i det mindste dette flerkolonneindeks:

CRATE INDEX foo_idx ON tbl (sn, ts);

Eller på (sn, ts, vin1, vin2, vin3) at tillade kun indeksscanninger - hvis nogle forudsætninger er opfyldt, og især hvis tabelrækkerne er meget bredere end i demoen.

Nært beslægtet:

Baseret på din oprindelige tabel

Som anmodet og præciseret i kommentaren , og senere opdateret igen i spørgsmålet til at inkludere kolonnerne mac og loc . Jeg antager, at du vil have separate gennemsnit pr. (mac, loc) .

dato og tid er stadig separate kolonner, vin* kolonner er typen float , og ekskluder tidsintervaller uden rækker:

Den opdaterede forespørgsel flytter også sæt-retur-funktionen generate_series() til FROM liste, som er renere før 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;

Opret et flerkolonne udtryksindeks for at understøtte dette:

CRATE INDEX bar_idx ON tbl (sn, (date+time));

db<>fiddle her

Men jeg vil meget hellere bruge timestamp hele tiden.




  1. Procedure for at opdatere en tabel, som allerede er udfyldt

  2. H2 postgresql-tilstand virker ikke for mig

  3. PHP MYSQL grupperangeringsforespørgsel

  4. postgres truncate er langsom