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æ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'