sql >> Database >  >> RDS >> PostgreSQL

De beste manier om records te tellen met willekeurige tijdsintervallen in Rails+Postgres

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 bovengrens NULL .

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'



  1. Databaseschema, automatisch verhogen

  2. Cumulatief totaal aantal gebruikers per dag in MySQL krijgen

  3. Oracle EXPAND_SQL_TEXT gebruiken

  4. Een nieuwe waarde toevoegen aan een bestaand ENUM-type