PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Menggunakan kolom yang sama beberapa kali dalam klausa WHERE

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


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Dasar-dasar Manajemen Skema PostgreSQL

  2. Apa itu PostgreSQL?

  3. Nilai kunci duplikat IntegrityError melanggar batasan unik - Django/postgres

  4. Bagaimana cara menghapus sejumlah baris tetap dengan penyortiran di PostgreSQL?

  5. Amazon RDS untuk Alternatif PostgreSQL - ClusterControl untuk PostgreSQL