W PostgreSQL zwykle występuje niewielka różnica przy rozsądnych długościach list, chociaż IN
jest znacznie czystszy koncepcyjnie. Bardzo długie AND ... <> ...
listy i bardzo długie NOT IN
wykazy obie działają fatalnie, z AND
znacznie gorzej niż NOT IN
.
W obu przypadkach, jeśli są one wystarczająco długie, abyś mógł zadać pytanie, powinieneś zamiast tego wykonać test blokujący łączenie lub wykluczanie podzapytań na liście wartości.
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);
lub:
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;
(W nowoczesnych wersjach Pg obie i tak wygenerują ten sam plan zapytań).
Jeśli lista wartości jest wystarczająco długa (wiele dziesiątek tysięcy elementów), analiza zapytań może zacząć mieć znaczny koszt. W tym momencie powinieneś rozważyć utworzenie TEMPORARY
tabela, COPY
danych, aby je wykluczyć, prawdopodobnie tworząc na nich indeks, a następnie stosując jedno z powyższych podejść w tabeli tymczasowej zamiast 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;
gdzie exclude
to lista wartości do pominięcia.
Następnie porównuję następujące podejścia na tych samych danych ze wszystkimi wynikami w milisekundach:
NOT IN
lista:3424.596AND ...
lista:80173.823VALUES
oparteJOIN
wykluczenie:20.727VALUES
wykluczenie na podstawie podzapytania:20.495- Oparte na tabeli
JOIN
, brak indeksu na byłej liście:25.183 - Na podstawie tabeli podzapytań, brak indeksu na byłej liście:23.985
... dzięki czemu podejście oparte na CTE jest ponad trzy tysiące razy szybsze niż AND
listy i 130 razy szybciej niż NOT IN
lista.
Kod tutaj:https://gist.github.com/ringerc/5755247 (chrońcie oczy, którzy podążacie za tym linkiem).
Dla tego rozmiaru zbioru danych dodanie indeksu na liście wykluczeń nie ma żadnego znaczenia.
Uwagi:
IN
lista wygenerowana za pomocąSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
lista wygenerowana za pomocąSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Wykluczanie podzapytań i tabel na podstawie złączeń było prawie takie samo w przypadku powtarzających się przebiegów.
- Badanie planu pokazuje, że Pg tłumaczy
NOT IN
do<> ALL
Więc... widać, że jest naprawdę ogromny przerwa między IN
i AND
listy vs robienie prawidłowego sprzężenia. Zaskoczyło mnie to, jak szybko robię to z CTE przy użyciu VALUES
lista była ... analizowała VALUES
lista nie zajęła prawie wcale czasu, wykonując to samo lub nieco szybciej niż podejście tabeli w większości testów.
Byłoby miło, gdyby PostgreSQL mógł automatycznie rozpoznać niedorzecznie długi IN
klauzula lub łańcuch podobnych AND
warunków i przełącz się na inteligentniejsze podejście, takie jak zrobienie sprzężenia haszowanego lub niejawne przekształcenie go w węzeł CTE. W tej chwili nie wie, jak to zrobić.
Zobacz też:
- ten przydatny post na blogu, który Magnus Hagander napisał na ten temat