Gelukkig gebruik je PostgreSQL. De vensterfunctie generate_series()
is je vriend.
Testcase
Gezien de volgende testtabel (die u had moeten verstrekken):
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';
Eén afspraak voor elke 7 minuten (plus 0 tot 7 minuten, willekeurig).
Basisoplossing
Deze query telt gebeurtenissen voor elk willekeurig tijdsinterval. 17 minuten in het voorbeeld:
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;
-
De query haalt minimale en maximale
ts
van de basistabel om het volledige tijdbereik te dekken. U kunt in plaats daarvan een willekeurig tijdbereik gebruiken. -
Geef elke tijdsinterval indien nodig.
-
Produceert één rij voor elke tijdslot. Als er tijdens dat interval geen gebeurtenis heeft plaatsgevonden, is de telling
0
. -
Zorg ervoor dat u boven- en ondergrens . hanteert correct:
- Onverwachte resultaten van SQL-query met TUSSEN tijdstempels
-
De vensterfunctie
lead()
heeft een vaak over het hoofd geziene functie:het kan een standaard bieden voor wanneer er geen leidende rij bestaat. Het verstrekken van'infinity'
in het voorbeeld. Anders zou het laatste interval worden afgebroken met een bovengrensNULL
.
Minimaal equivalent
De bovenstaande query gebruikt een CTE en lead()
en uitgebreide syntaxis. Elegant en misschien gemakkelijker te begrijpen, maar een beetje duurder. Hier is een kortere, snellere, minimale versie:
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;
Voorbeeld voor 'elke 15 minuten in de afgelopen week'`
En formatteren met 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;
Nog steeds ORDER BY
en GROUP BY
op de onderliggende tijdstempel waarde , niet op de geformatteerde string. Dat is sneller en betrouwbaarder.
db<>viool hier
Gerelateerd antwoord met een lopende telling over het tijdsbestek:
- PostgreSQL:lopende telling van rijen voor een zoekopdracht 'per minuut'