sql >> Databasteknik >  >> RDS >> PostgreSQL

Bästa sättet att räkna poster efter godtyckliga tidsintervall i Rails+Postgres

Lyckligtvis använder du PostgreSQL. Fönsterfunktionen generate_series() är din vän.

Testfall

Med tanke på följande testtabell (som du borde ha tillhandahållit):

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

Ett evenemang var 7:e minut (plus 0 till 7 minuter, slumpmässigt).

Grundläggande lösning

Denna fråga räknar händelser för godtyckliga tidsintervall. 17 minuter i exemplet:

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;
  • Frågan hämtar minsta och högsta ts från bastabellen för att täcka hela tidsintervallet. Du kan använda ett godtyckligt tidsintervall istället.

  • Ange alla tidsintervall efter behov.

  • Ger en rad för varje tidslucka. Om ingen händelse inträffade under det intervallet är räkningen 0 .

  • Var noga med att hantera övre och nedre gräns korrekt:

    • Oväntade resultat från SQL-fråga med BETWEEN tidsstämplar
  • Fönsterfunktionen lead() har en ofta förbisedd funktion:den kan tillhandahålla en standard för när det inte finns någon inledande rad. Tillhandahåller 'infinity' i exemplet. Annars skulle det sista intervallet skäras av med en övre gräns NULL .

Minsta motsvarighet

Ovanstående fråga använder en CTE och lead() och utförlig syntax. Elegant och kanske lättare att förstå, men lite dyrare. Här är en kortare, snabbare, 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;

Exempel för "var 15:e minut under den senaste veckan"`

Och 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;

Fortfarande ORDER BY och GROUP BY på den underliggande tidsstämpeln värde , inte på den formaterade strängen. Det är snabbare och mer pålitligt.

db<>spela här

Relaterat svar som ger en löpande räkning över tidsramen:

  • PostgreSQL:löpande antal rader för en fråga "per minut"



  1. CONV() – Konvertera tal mellan olika baser i MySQL

  2. Guide för CTE i SQL Server

  3. En teknisk jämförelse:Microsoft Access 2016 vs SQL Server 2016

  4. Hur man genererar DDL-skript (Skapa) från SQL Server Management Studio (SSMS) - SQL Server / TSQL Tutorial Del 17