Pomocí několika různých funkcí okna a dvou dílčích dotazů by to mělo fungovat slušně rychle:
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;
Pomocí ts
jako název pro sloupec časového razítka.
Za předpokladu ts
být jedinečný – a indexovaný (jedinečné omezení to dělá automaticky).
V testu se skutečnou tabulkou s 50 000 řádky potřeboval pouze jediné prohledání indexu . Takže by měl být slušně rychlý i u velkých stolů. Pro srovnání, váš dotaz s připojením / odlišným neskončil po minutě (jak se očekávalo).
Dokonce i optimalizovaná verze, která se zabývá jedním křížovým spojením najednou (levé spojení s téměř omezující podmínkou je ve skutečnosti omezené cross join) neskončil po minutě.
Pro nejlepší výkon s velkým stolem vylaďte nastavení paměti, zejména pro work_mem
(pro velké operace řazení). Zvažte dočasné nastavení (mnohem) vyšší pro vaši relaci, pokud můžete ušetřit RAM. Přečtěte si více zde a zde.
Jak?
-
V dílčím dotazu
sub1
podívejte se na událost z předchozího řádku a ponechte ji, pouze pokud se změnila, čímž označíte první prvek nové skupiny. Zároveň získejteid
předchozího a následujícího řádku (pre_id
,post_id
). -
V dílčím dotazu
sub2
,count()
počítá pouze nenulové hodnoty. Výslednýgrp
označuje partnery v blocích po sobě jdoucích stejných událostí. -
Ve finále
SELECT
, vezměte prvnípre_id
a poslednípost_id
na skupinu pro každý řádek, abyste dosáhli požadovaného výsledku.
Ve skutečnosti by to mělo být ještě rychlejší ve vnějšímSELECT
:last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... protože pořadí řazení okna souhlasí s oknem pro
pre_id
, takže je potřeba pouze jeden druh. Zdá se, že to potvrzuje rychlý test. Více o této definici rámce.
SQL Fiddle.