Ini adalah kasus pembagian relasional. Saya menambahkan tag.
Indeks
Dengan asumsi batasan PK atau UNIK pada USER_PROPERTY_MAP(property_value_id, user_id)
- kolom dalam rangka ini untuk membuat kueri saya cepat. Terkait:
- Apakah indeks komposit juga bagus untuk kueri di bidang pertama?
Anda juga harus memiliki indeks pada PROPERTY_VALUE(value, property_name_id, id)
. Sekali lagi, kolom dalam urutan ini. Tambahkan kolom terakhir id
hanya jika Anda mendapatkan pindaian hanya indeks darinya.
Untuk sejumlah properti tertentu
Ada banyak cara untuk menyelesaikannya. Ini harus menjadi salah satu yang paling sederhana dan tercepat untuk tepat dua properti:
SELECT u.*
FROM users u
JOIN user_property_map up1 ON up1.user_id = u.id
JOIN user_property_map up2 USING (user_id)
WHERE up1.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 1 AND value = '101')
AND up2.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 2 AND value = '102')
-- AND u.user_name = 'user1' -- more filters?
-- AND u.city = 'city1'
Tidak mengunjungi tabel PROPERTY_NAME
, karena Anda tampaknya telah menyelesaikan nama properti menjadi ID, sesuai dengan kueri contoh Anda. Jika tidak, Anda dapat menambahkan gabungan ke PROPERTY_NAME
di setiap subkueri.
Kami telah mengumpulkan gudang teknik di bawah pertanyaan terkait ini:
- Cara memfilter hasil SQL dalam relasi has-many-through
Untuk jumlah properti yang tidak diketahui
@Mike dan @Valera memiliki pertanyaan yang sangat berguna dalam jawaban masing-masing. Untuk membuat ini lebih dinamis :
WITH input(property_name_id, value) AS (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
)
SELECT *
FROM users u
JOIN (
SELECT up.user_id AS id
FROM input
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
GROUP BY 1
HAVING count(*) = (SELECT count(*) FROM input)
) sub USING (id);
Hanya tambahkan / hapus baris dari VALUES
ekspresi. Atau hapus WITH
klausa dan JOIN
untuk tanpa filter properti sama sekali.
Masalah dengan kelas kueri ini (menghitung semua kecocokan sebagian) adalah kinerja . Permintaan pertama saya kurang dinamis, tetapi biasanya jauh lebih cepat. (Cukup uji dengan EXPLAIN ANALYZE
.) Khusus untuk tabel yang lebih besar dan jumlah properti yang terus bertambah.
Yang terbaik dari kedua dunia?
Solusi dengan CTE rekursif ini harus menjadi kompromi yang baik:dan fast yang cepat dinamis:
WITH RECURSIVE input AS (
SELECT count(*) OVER () AS ct
, row_number() OVER () AS rn
, *
FROM (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
) i (property_name_id, value)
)
, rcte AS (
SELECT i.ct, i.rn, up.user_id AS id
FROM input i
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
WHERE i.rn = 1
UNION ALL
SELECT i.ct, i.rn, up.user_id
FROM rcte r
JOIN input i ON i.rn = r.rn + 1
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
AND up.user_id = r.id
)
SELECT u.*
FROM rcte r
JOIN users u USING (id)
WHERE r.ct = r.rn; -- has all matches
dbfiddle di sini
Manual tentang CTE rekursif.
Kompleksitas tambahan tidak membayar untuk tabel kecil di mana overhead tambahan melebihi manfaat apa pun atau perbedaannya dapat diabaikan sejak awal. Namun skalanya jauh lebih baik dan semakin unggul daripada teknik "menghitung" dengan tabel yang terus bertambah dan filter properti yang jumlahnya terus bertambah.
Teknik menghitung harus mengunjungi semua baris di user_property_map
untuk semua filter properti yang diberikan, sementara kueri ini (serta kueri pertama) dapat menghilangkan pengguna yang tidak relevan lebih awal.
Mengoptimalkan kinerja
Dengan statistik tabel saat ini (pengaturan yang wajar, autovacuum
berjalan), Postgres memiliki pengetahuan tentang "nilai paling umum" di setiap kolom dan akan menyusun ulang gabungan dalam kueri pertama untuk mengevaluasi filter properti yang paling selektif terlebih dahulu (atau setidaknya bukan yang paling tidak selektif). Hingga batas tertentu:join_collapse_limit
. Terkait:
- Postgresql join_collapse_limit dan waktu untuk perencanaan kueri
- Mengapa sedikit perubahan pada istilah penelusuran sangat memperlambat kueri?
Intervensi "deus-ex-machina" ini tidak mungkin dilakukan dengan kueri ke-3 (CTE rekursif). Untuk membantu kinerja (mungkin banyak), Anda harus menempatkan filter yang lebih selektif terlebih dahulu. Tetapi bahkan dengan pemesanan terburuk sekalipun, itu akan tetap mengungguli penghitungan kueri.
Terkait:
- Periksa target statistik di PostgreSQL
Lebih banyak detail mengerikan:
- Indeks parsial PostgreSQL tidak digunakan saat dibuat pada tabel dengan data yang ada
Penjelasan lebih lanjut di manual:
- Statistik yang Digunakan oleh Perencana