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

PostgreSQL-Fensterfunktion:Partition durch Vergleich

Mit mehreren verschiedenen Fensterfunktionen und zwei Unterabfragen sollte dies recht schnell funktionieren:

WITH events(id, event, ts) AS (
  VALUES
   (1, 12, '2014-03-19 08:00:00'::timestamp)
  ,(2, 12, '2014-03-19 08:30:00')
  ,(3, 13, '2014-03-19 09:00:00')
  ,(4, 13, '2014-03-19 09:30:00')
  ,(5, 12, '2014-03-19 10:00:00')
   )
SELECT first_value(pre_id)  OVER (PARTITION BY grp ORDER BY ts)      AS pre_id
     , id, ts
     , first_value(post_id) OVER (PARTITION BY grp ORDER BY ts DESC) AS post_id
FROM  (
   SELECT *, count(step) OVER w AS grp
   FROM  (
      SELECT id, ts
           , NULLIF(lag(event) OVER w, event) AS step
           , lag(id)  OVER w AS pre_id
           , lead(id) OVER w AS post_id
      FROM   events
      WINDOW w AS (ORDER BY ts)
      ) sub1
   WINDOW w AS (ORDER BY ts)
   ) sub2
ORDER  BY ts;

Verwenden von ts als Name für die Zeitstempelspalte.
Angenommen ts eindeutig sein - und indiziert (Eine eindeutige Einschränkung macht das automatisch).

In einem Test mit einer realen Tabelle mit 50.000 Zeilen war nur ein einziger Index-Scan erforderlich . Sollte also auch bei großen Tischen anständig schnell sein. Im Vergleich dazu wurde Ihre Abfrage mit join / distinct nicht nach einer Minute beendet (wie erwartet).
Sogar eine optimierte Version, die jeweils nur einen Cross-Join behandelt (der linke Join mit kaum einer einschränkenden Bedingung ist effektiv eine begrenzte cross join) wurde nach einer Minute nicht beendet.

Optimieren Sie für eine optimale Leistung mit einer großen Tabelle Ihre Speichereinstellungen, insbesondere für work_mem (für große Sortieroperationen). Erwägen Sie, es für Ihre Sitzung vorübergehend (viel) höher einzustellen, wenn Sie den Arbeitsspeicher entbehren können. Lesen Sie mehr hier und hier.

Wie?

  1. In Unterabfrage sub1 Sehen Sie sich das Ereignis aus der vorherigen Zeile an und behalten Sie es nur, wenn es sich geändert hat, und markieren Sie so das erste Element einer neuen Gruppe. Holen Sie sich gleichzeitig die id der vorherigen und der nächsten Zeile (pre_id , post_id ).

  2. In Unterabfrage sub2 , count() zählt nur Nicht-Null-Werte. Die resultierende grp markiert Peers in Blöcken aufeinanderfolgender gleicher Ereignisse.

  3. Im letzten SELECT , nehmen Sie die erste pre_id und die letzte post_id pro Gruppe für jede Zeile, um zum gewünschten Ergebnis zu kommen.
    Eigentlich sollte das im äußeren SELECT noch schneller gehen :

     last_value(post_id) OVER (PARTITION BY grp ORDER BY ts
                               RANGE BETWEEN UNBOUNDED PRECEDING
                                     AND     UNBOUNDED FOLLOWING) AS post_id
    

    ... da die Sortierreihenfolge des Fensters mit dem Fenster für pre_id übereinstimmt , sodass nur eine einzige Sortierung erforderlich ist. Ein Schnelltest scheint es zu bestätigen. Mehr über diese Rahmendefinition.

SQL-Geige.