PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Meilleur moyen de compter les enregistrements par intervalles de temps arbitraires dans Rails + Postgres

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érieure NULL .

É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"