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
tsfra 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ænseNULL.
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'