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

Bedste måde at tælle poster efter vilkårlige tidsintervaller i Rails+Postgres

Heldigvis bruger du PostgreSQL. Vinduesfunktionen generate_series() er din ven.

Testcase

Givet følgende testtabel (som du skulle have givet):

CREATE TABLE event(event_id serial, ts timestamp);
INSERT INTO event (ts)
SELECT generate_series(timestamp '2018-05-01'
                     , timestamp '2018-05-08'
                     , interval '7 min') + random() * interval '7 min';

Én begivenhed for hvert 7. minut (plus 0 til 7 minutter, tilfældigt).

Grundlæggende løsning

Denne forespørgsel tæller hændelser for ethvert vilkårligt tidsinterval. 17 minutter i eksemplet:

WITH grid AS (
   SELECT start_time
        , lead(start_time, 1, 'infinity') OVER (ORDER BY start_time) AS end_time
   FROM  (
      SELECT generate_series(min(ts), max(ts), interval '17 min') AS start_time
      FROM   event
      ) sub
   )
SELECT start_time, count(e.ts) AS events
FROM   grid       g
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.end_time
GROUP  BY start_time
ORDER  BY start_time;
  • Forespørgslen henter minimum og maksimum ts fra basistabellen for at dække hele tidsintervallet. Du kan bruge et vilkårligt tidsinterval i stedet.

  • Giv enhver tidsinterval efter behov.

  • Producerer én række for hver tidsvindue. Hvis der ikke skete nogen hændelse i det interval, er tælleren 0 .

  • Sørg for at håndtere øvre og nedre grænse korrekt:

    • Uventede resultater fra SQL-forespørgsel med BETWEEN tidsstempler
  • Vinduesfunktionen lead() har en ofte overset funktion:den kan give en standard for, når der ikke findes nogen forreste række. Leverer 'infinity' i eksemplet. Ellers ville det sidste interval blive afskåret med en øvre grænse NULL .

Minimale ækvivalent

Ovenstående forespørgsel bruger en CTE og lead() og udførlig syntaks. Elegant og måske lettere at forstå, men lidt dyrere. Her er en kortere, hurtigere, minimal version:

SELECT start_time, count(e.ts) AS events
FROM  (SELECT generate_series(min(ts), max(ts), interval '17 min') FROM event) g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '17 min'
GROUP  BY 1
ORDER  BY 1;

Eksempel på "hvert 15. minut i den seneste uge"`

Og formatering med to_char() .

SELECT to_char(start_time, 'YYYY-MM-DD HH24:MI'), count(e.ts) AS events
FROM   generate_series(date_trunc('day', localtimestamp - interval '7 days')
                     , localtimestamp
                     , interval '15 min') g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '15 min'
GROUP  BY start_time
ORDER  BY start_time;

Stadig ORDER BY og GROUP BY på det underliggende tidsstempel værdi , ikke på den formaterede streng. Det er hurtigere og mere pålideligt.

db<>spil her

Relateret svar, der producerer en løbende optælling over tidsrammen:

  • PostgreSQL:kørende antal rækker for en forespørgsel 'efter minut'



  1. Ret "FEJL 1054 (42S22):Ukendt kolonne "..." i "ordre clause", når du bruger UNION i MySQL

  2. SQL NVARCHAR- og VARCHAR-grænser

  3. Installation og konfiguration af MySQL på Ubuntu 20.04

  4. LINQ til SQL flere tabeller venstre ydre joinforbindelse