PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Abfrage der Anzahl unterschiedlicher Werte in einem gleitenden Datumsbereich

Testfall:

CREATE TABLE tbl (date date, email text);
INSERT INTO tbl VALUES
  ('2012-01-01', '[email protected]')
, ('2012-01-01', '[email protected]')
, ('2012-01-01', '[email protected]')
, ('2012-01-02', '[email protected]')
, ('2012-01-02', '[email protected]')
, ('2012-01-03', '[email protected]')
, ('2012-01-04', '[email protected]')
, ('2012-01-05', '[email protected]')
, ('2012-01-05', '[email protected]')
, ('2012-01-06', '[email protected]')
, ('2012-01-06', '[email protected]')
, ('2012-01-06', '[email protected]`')
;

Abfrage - gibt nur Tage zurück, an denen ein Eintrag in tbl vorhanden ist :

SELECT date
     ,(SELECT count(DISTINCT email)
       FROM   tbl
       WHERE  date BETWEEN t.date - 2 AND t.date -- period of 3 days
      ) AS dist_emails
FROM   tbl t
WHERE  date BETWEEN '2012-01-01' AND '2012-01-06'  
GROUP  BY 1
ORDER  BY 1;

Oder - geben Sie alle Tage zurück im angegebenen Bereich, auch wenn es keine Zeilen für den Tag gibt:

SELECT date
     ,(SELECT count(DISTINCT email)
       FROM   tbl
       WHERE  date BETWEEN g.date - 2 AND g.date
      ) AS dist_emails
FROM  (SELECT generate_series(timestamp '2012-01-01'
                            , timestamp '2012-01-06'
                            , interval  '1 day')::date) AS g(date);

db<>hier fummeln

Ergebnis:

day        | dist_emails
-----------+------------
2012-01-01 | 3
2012-01-02 | 3
2012-01-03 | 3
2012-01-04 | 3
2012-01-05 | 1
2012-01-06 | 2

Das klang zunächst wie ein Job für Fensterfunktionen, aber ich habe keine Möglichkeit gefunden, den passenden Fensterrahmen zu definieren. Außerdem laut Dokumentation:

Aggregatfensterfunktionen erlauben im Gegensatz zu normalen Aggregatfunktionen DISTINCT nicht oder ORDER BY innerhalb der Funktionsargumentliste verwendet werden.

Also habe ich es stattdessen mit korrelierten Unterabfragen gelöst. Ich denke, das ist der klügste Weg.

Übrigens, "zwischen besagtem Datum und vor 3 Tagen" wäre ein Zeitraum von 4 Tage. Ihre Definition ist da widersprüchlich.

Etwas kürzer, aber langsamer für ein paar Tage:

SELECT g.date, count(DISTINCT email) AS dist_emails
FROM  (SELECT generate_series(timestamp '2012-01-01'
                            , timestamp '2012-01-06'
                            , interval  '1 day')::date) AS g(date)
LEFT   JOIN tbl t ON t.date BETWEEN g.date - 2 AND g.date
GROUP  BY 1
ORDER  BY 1;

Verwandte:

  • Generieren von Zeitreihen zwischen zwei Daten in PostgreSQL
  • Rollende Anzahl von Zeilen innerhalb eines Zeitintervalls