В PostgreSQL обикновено има доста малка разлика при разумни дължини на списъка, въпреки че IN
е много по-чист концептуално. Много дълго AND ... <> ...
списъци и много дълги NOT IN
И двата списъка се представят ужасно, с AND
много по-лошо от NOT IN
.
И в двата случая, ако са достатъчно дълги, за да зададете въпроса, вместо това трябва да правите тест за изключване срещу присъединяване или подзаявка върху списък със стойности.
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);
или:
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;
(В съвременните версии на Pg и двете ще произвеждат един и същ план за заявка.
Ако списъкът със стойности е достатъчно дълъг (много десетки хиляди артикули), тогава анализът на заявката може да започне да има значителни разходи. На този етап трябва да помислите за създаване на TEMPORARY
таблица, COPY
въвеждане на данните за изключване в него, евентуално създаване на индекс върху тях, след което използване на един от горните подходи за временната таблица вместо CTE.
Демо:
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;
където exclude
е списъкът със стойности за пропускане.
След това сравнявам следните подходи на едни и същи данни с всички резултати в милисекунди:
NOT IN
списък:3424.596AND ...
списък:80173.823VALUES
базиран наJOIN
изключване:20.727VALUES
базирано изключване на подзаявка:20.495- Базирано на таблица
JOIN
, без индекс в бившия списък:25.183 - Въз основа на таблица на подзаявки, без индекс в предишния списък:23,985
... което прави базирания на CTE подход над три хиляди пъти по-бърз от AND
списък и 130 пъти по-бързо от NOT IN
списък.
Код тук:https://gist.github.com/ringerc/5755247 (закрилете очите си, вие, които следвате тази връзка).
За този размер на набора от данни добавянето на индекс в списъка за изключване не направи разлика.
Бележки:
IN
списък, генериран сSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
списък, генериран сSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Изключването на таблици, базирано на подзаявка и присъединяване, беше почти едно и също при многократно изпълнение.
- Разглеждането на плана показва, че Pg превежда
NOT IN
до<> ALL
Така че... можете да видите, че има наистина огромен разлика между двата IN
и AND
списъци срещу правене на правилно присъединяване. Това, което ме изненада, беше колко бързо го направих с CTE с помощта на VALUES
списъкът беше ... синтактичен анализ на VALUES
списъкът не отне почти никакво време, изпълнявайки същото или малко по-бързо от подходът на таблицата в повечето тестове.
Би било хубаво, ако PostgreSQL може автоматично да разпознае нелепо дълъг IN
клауза или верига от подобни AND
условия и преминете към по-интелигентен подход като извършване на хеширано присъединяване или имплицитно превръщането му в CTE възел. В момента то не знае как да направи това.
Вижте също:
- тази удобна публикация в блога Магнус Хагандер написа по темата