V PostgreSQL je obvykle poměrně malý rozdíl v rozumné délce seznamu, i když IN
je koncepčně mnohem čistší. Velmi dlouhé AND ... <> ...
seznamy a velmi dlouhé NOT IN
oba seznamy fungují strašně, s AND
mnohem horší než NOT IN
.
V obou případech, pokud jsou dostatečně dlouhé na to, abyste otázku položili, měli byste místo toho provést test vyloučení spojení nebo poddotazu nad seznamem hodnot.
WITH excluded(item) AS (
VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT *
FROM thetable t
WHERE NOT EXISTS(SELECT 1 FROM excluded e WHERE t.item = e.item);
nebo:
WITH excluded(item) AS (
VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT *
FROM thetable t
LEFT OUTER JOIN excluded e ON (t.item = e.item)
WHERE e.item IS NULL;
(Na moderních verzích Pg oba stejně vytvoří stejný plán dotazů).
Pokud je seznam hodnot dostatečně dlouhý (mnoho desítek tisíc položek), může analýza dotazu začít mít značné náklady. V tomto okamžiku byste měli zvážit vytvoření TEMPORARY
tabulka, COPY
data, která do ní chcete vyloučit, případně pro ně vytvořit index a poté použít jeden z výše uvedených přístupů na dočasné tabulce namísto CTE.
Demo:
CREATE UNLOGGED TABLE exclude_test(id integer primary key);
INSERT INTO exclude_test(id) SELECT generate_series(1,50000);
CREATE TABLE exclude AS SELECT x AS item FROM generate_series(1,40000,4) x;
kde exclude
je seznam hodnot, které se mají vynechat.
Poté porovnám následující přístupy na stejných datech se všemi výsledky v milisekundách:
NOT IN
seznam:3424,596AND ...
seznam:80173.823VALUES
na základěJOIN
vyloučení:20,727VALUES
na základě vyloučení poddotazu:20,495- Tabulkové
JOIN
, žádný index na ex-seznamu:25,183 - Na základě tabulky dílčích dotazů, žádný index na seznamu ex:23,985
... přístup založený na CTE je více než třitisíckrát rychlejší než AND
seznam a 130krát rychlejší než NOT IN
seznam.
Kód zde:https://gist.github.com/ringerc/5755247 (zachraňte si oči, vy, kteří sledujete tento odkaz).
Pro velikost této datové sady nemělo přidání indexu na seznam vyloučení žádný rozdíl.
Poznámky:
IN
seznam vygenerován pomocíSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
seznam vygenerovaný pomocíSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Vyloučení poddotazů a vyloučení tabulek na základě spojení bylo při opakovaných spuštěních téměř stejné.
- Zkoumání plánu ukazuje, že Pg překládá
NOT IN
na<> ALL
Takže... můžete vidět, že je toho opravdu obrovské mezera mezi oběma IN
a AND
seznamy vs provedení správného spojení. Překvapilo mě, jak rychle se to dělá s CTE pomocí VALUES
seznam byl ... analýzou VALUES
seznam nezabral téměř vůbec čas a fungoval stejně nebo o něco rychleji než tabulkový přístup ve většině testů.
Bylo by hezké, kdyby PostgreSQL dokázal automaticky rozpoznat nesmyslně dlouhé IN
klauzule nebo řetězec podobných AND
a přejít na chytřejší přístup, jako je hašované spojení nebo jeho implicitní přeměna na uzel CTE. Právě teď neví, jak to udělat.
Viz také:
- na toto téma napsal tento užitečný blogový příspěvek Magnus Hagander