Di PostgreSQL biasanya ada perbedaan yang cukup kecil pada panjang daftar yang masuk akal, meskipun IN
jauh lebih bersih secara konseptual. Sangat panjang AND ... <> ...
daftar dan sangat panjang NOT IN
daftar keduanya berkinerja buruk, dengan AND
jauh lebih buruk daripada NOT IN
.
Dalam kedua kasus tersebut, jika cukup lama bagi Anda untuk mengajukan pertanyaan, Anda harus melakukan tes pengecualian anti-gabung atau subkueri melalui daftar nilai.
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);
atau:
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;
(Pada versi Pg modern, keduanya akan menghasilkan paket kueri yang sama).
Jika daftar nilai cukup panjang (puluhan ribu item) maka penguraian kueri mungkin mulai memiliki biaya yang signifikan. Pada titik ini Anda harus mempertimbangkan untuk membuat TEMPORARY
tabel, COPY
ing data untuk dikecualikan ke dalamnya, mungkin membuat indeks di atasnya, kemudian menggunakan salah satu pendekatan di atas pada tabel temp alih-alih 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;
di mana exclude
adalah daftar nilai yang harus dihilangkan.
Saya kemudian membandingkan pendekatan berikut pada data yang sama dengan semua hasil dalam milidetik:
NOT IN
daftar:3424.596AND ...
daftar:80173.823VALUES
berdasarkanJOIN
pengecualian:20.727VALUES
pengecualian subkueri berbasis:20.495- Berbasis tabel
JOIN
, tidak ada indeks pada daftar sebelumnya:25.183 - Berdasarkan tabel subkueri, tidak ada indeks pada daftar sebelumnya:23.985
... membuat pendekatan berbasis CTE lebih dari tiga ribu kali lebih cepat daripada AND
daftar dan 130 kali lebih cepat daripada NOT IN
daftar.
Kodenya di sini:https://gist.github.com/ringerc/5755247 (tutupi matamu, kamu yang mengikuti tautan ini).
Untuk ukuran kumpulan data ini, menambahkan indeks pada daftar pengecualian tidak ada bedanya.
Catatan:
IN
daftar yang dihasilkan denganSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
daftar dibuat denganSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Pengecualian tabel berbasis subkueri dan gabungan hampir sama di seluruh proses berulang.
- Pemeriksaan rencana menunjukkan bahwa Pg menerjemahkan
NOT IN
ke<> ALL
Jadi... Anda dapat melihat bahwa ada yang benar-benar besar kesenjangan antara keduanya IN
dan AND
daftar vs melakukan gabungan yang benar. Yang mengejutkan saya adalah seberapa cepat melakukannya dengan CTE menggunakan VALUES
list adalah ... menguraikan VALUES
list hampir tidak memakan waktu sama sekali, melakukan hal yang sama atau sedikit lebih cepat dari pendekatan tabel di sebagian besar pengujian.
Alangkah baiknya jika PostgreSQL dapat secara otomatis mengenali IN
. yang sangat panjang klausa atau rantai dari AND
. yang serupa kondisi dan beralih ke pendekatan yang lebih cerdas seperti melakukan gabungan hash atau secara implisit mengubahnya menjadi simpul CTE. Saat ini ia tidak tahu bagaimana melakukannya.
Lihat juga:
- postingan blog praktis yang ditulis Magnus Hagander tentang topik ini