sql >> Databasteknik >  >> RDS >> PostgreSQL

PostgreSQL-fönsterfunktion:partition i jämförelse

Genom att använda flera olika fönsterfunktioner och två underfrågor borde detta fungera hyfsat snabbt:

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;

Använder ts som namn för tidsstämpelkolumnen.
Anta ts att vara unik – och indexerad (en unik begränsning gör det automatiskt).

I ett test med en tabell i verkligheten med 50 000 rader behövdes bara en enda indexskanning . Så, bör vara hyfsat snabb även med stora bord. Som jämförelse slutfördes inte din fråga med join / distinct efter en minut (som förväntat).
Även en optimerad version, som hanterar en korsfogning i taget (vänsterfogningen med knappast ett begränsande villkor är i praktiken en begränsad cross join) avslutades inte efter en minut.

För bästa prestanda med ett stort bord, justera dina minnesinställningar, särskilt för work_mem (för stora sorters operationer). Överväg att ställa in den (mycket) högre för din session tillfälligt om du kan spara RAM-minnet. Läs mer här och här.

Hur?

  1. I underfrågan sub1 titta på händelsen från föregående rad och behåll den bara om den har ändrats, vilket markerar det första elementet i en ny grupp. Skaffa samtidigt id av föregående och nästa rad (pre_id , post_id ).

  2. I underfrågan sub2 , count() räknar endast icke-nullvärden. Den resulterande grp markerar kamrater i block av på varandra följande samma händelser.

  3. I den sista SELECT , ta det första pre_id och det sista post_id per grupp för varje rad för att komma fram till önskat resultat.
    Egentligen borde detta vara ännu snabbare i den yttre SELECT :

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

    ... eftersom sorteringsordningen för fönstret överensstämmer med fönstret för pre_id , så bara en enda sortering behövs. Ett snabbt test verkar bekräfta det. Mer om denna ramdefinition.

SQL-fiol.




  1. Hur man får en rest med MOD() i PostgreSQL, MS SQL Server och MySQL

  2. Överväganden kring kolumnordning i index och sorteringar

  3. MySQL jämför DATE-sträng med sträng från DATETIME-fältet

  4. PARTITION BY med och utan KEEP i Oracle