Heureusement, vous utilisez PostgreSQL. La fonction de fenêtre generate_series()
est votre ami.
Cas de test
Étant donné le tableau de test suivant (que vous aurait dû fournir):
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';
Un événement toutes les 7 minutes (plus 0 à 7 minutes, aléatoirement).
Solution de base
Cette requête compte les événements pour tout intervalle de temps arbitraire. 17 minutes dans l'exemple :
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;
-
La requête récupère les
ts
minimum et maximum de la table de base pour couvrir toute la plage horaire. Vous pouvez utiliser une plage de temps arbitraire à la place. -
Fournissez tout intervalle de temps au besoin.
-
Produit une ligne pour chaque créneau horaire. Si aucun événement ne s'est produit pendant cet intervalle, le nombre est
0
. -
Assurez-vous de gérer les limites supérieure et inférieure correctement :
- Résultats inattendus d'une requête SQL avec des horodatages BETWEEN
-
La fonction de fenêtre
lead()
a une fonctionnalité souvent négligée :il peut fournir une valeur par défaut lorsqu'il n'existe aucune ligne de tête. Fournir'infinity'
dans l'exemple. Sinon, le dernier intervalle serait coupé avec une limite supérieureNULL
.
Équivalent minimal
La requête ci-dessus utilise un CTE et lead()
et syntaxe verbeuse. Élégant et peut-être plus facile à comprendre, mais un peu plus cher. Voici une version plus courte, plus rapide et minimale :
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;
Exemple pour "toutes les 15 minutes de la semaine dernière"`
Et formater avec 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;
Toujours ORDER BY
et GROUP BY
sur la valeur d'horodatage sous-jacente , pas sur la chaîne formatée. C'est plus rapide et plus fiable.
db<>jouez ici
Réponse connexe produisant un compte courant sur la période :
- PostgreSQL :nombre de lignes en cours pour une requête "par minute"