Door verschillende vensterfuncties en twee subquery's te gebruiken, zou dit behoorlijk snel moeten werken:
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;
Met behulp van ts
als naam voor de tijdstempelkolom.
Ervan uitgaande dat ts
uniek zijn - en geïndexeerd (een unieke beperking doet dat automatisch).
In een test met een echte tabel met 50k rijen was er slechts een enkele indexscan nodig . Dus zou behoorlijk snel moeten zijn, zelfs met grote tafels. Ter vergelijking:uw zoekopdracht met join / distinct is niet na een minuut voltooid (zoals verwacht).
Zelfs een geoptimaliseerde versie, die met één cross join tegelijk te maken heeft (de linker join met nauwelijks een beperkende voorwaarde is in feite een beperkte cross join) was na een minuut niet klaar.
Voor de beste prestaties met een grote tafel, stem je je geheugeninstellingen af, in het bijzonder voor work_mem
(voor grote sorteerbewerkingen). Overweeg om deze tijdelijk (veel) hoger voor uw sessie in te stellen als u het RAM-geheugen kunt sparen. Lees hier en hier meer.
Hoe?
-
In subquery
sub1
kijk naar de gebeurtenis uit de vorige rij en bewaar die alleen als deze is gewijzigd, en markeer zo het eerste element van een nieuwe groep. Haal tegelijkertijd deid
. op van de vorige en de volgende rij (pre_id
,post_id
). -
In subquery
sub2
,count()
telt alleen niet-null-waarden. De resulterendegrp
markeert peers in blokken van opeenvolgende dezelfde gebeurtenissen. -
In de laatste
SELECT
, neem de eerstepre_id
en de laatstepost_id
per groep voor elke rij om tot het gewenste resultaat te komen.
Eigenlijk zou dit nog sneller moeten zijn in de buitensteSELECT
:last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... aangezien de sorteervolgorde van het venster overeenkomt met het venster voor
pre_id
, dus er is maar één sortering nodig. Een snelle test lijkt het te bevestigen. Meer over deze framedefinitie.
SQL Fiddle.