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

Kueri SQL untuk menemukan baris dengan sejumlah asosiasi tertentu

Ini adalah kasus - dengan tambahan persyaratan khusus bahwa percakapan yang sama tidak boleh memiliki tambahan pengguna.

Dengan asumsi adalah PK dari tabel "conversationUsers" yang memaksakan keunikan kombinasi, NOT NULL dan juga menyediakan indeks penting untuk kinerja secara implisit. Kolom PK multikolom di ini memesan! Jika tidak, Anda harus berbuat lebih banyak.
Tentang urutan kolom indeks:

Untuk kueri dasar, ada "brute force" pendekatan untuk menghitung jumlah pengguna yang cocok untuk semua percakapan dari semua pengguna tertentu dan kemudian memfilter percakapan yang cocok dengan semua pengguna tertentu. OK untuk tabel kecil dan/atau hanya larik masukan pendek dan/atau beberapa percakapan per pengguna, tetapi tidak dapat diskalakan dengan baik :

SELECT "conversationId"
FROM   "conversationUsers" c
WHERE  "userId" = ANY ('{1,4,6}'::int[])
GROUP  BY 1
HAVING count(*) = array_length('{1,4,6}'::int[], 1)
AND    NOT EXISTS (
   SELECT FROM "conversationUsers"
   WHERE  "conversationId" = c."conversationId"
   AND    "userId" <> ALL('{1,4,6}'::int[])
   );

Menghilangkan percakapan dengan pengguna tambahan dengan NOT EXISTS anti-semi-join. Selengkapnya:

Teknik alternatif:

Ada berbagai lainnya, (jauh) lebih cepat teknik kueri. Tapi yang tercepat tidak cocok untuk dinamis jumlah ID pengguna.

Untuk kueri cepat yang juga dapat menangani sejumlah ID pengguna yang dinamis, pertimbangkan CTE rekursif :

WITH RECURSIVE rcte AS (
   SELECT "conversationId", 1 AS idx
   FROM   "conversationUsers"
   WHERE  "userId" = ('{1,4,6}'::int[])[1]

   UNION ALL
   SELECT c."conversationId", r.idx + 1
   FROM   rcte                r
   JOIN   "conversationUsers" c USING ("conversationId")
   WHERE  c."userId" = ('{1,4,6}'::int[])[idx + 1]
   )
SELECT "conversationId"
FROM   rcte r
WHERE  idx = array_length(('{1,4,6}'::int[]), 1)
AND    NOT EXISTS (
   SELECT FROM "conversationUsers"
   WHERE  "conversationId" = r."conversationId"
   AND    "userId" <> ALL('{1,4,6}'::int[])
   );

Untuk kemudahan penggunaan, bungkus ini dalam suatu fungsi atau pernyataan yang disiapkan . Seperti:

PREPARE conversations(int[]) AS
WITH RECURSIVE rcte AS (
   SELECT "conversationId", 1 AS idx
   FROM   "conversationUsers"
   WHERE  "userId" = $1[1]

   UNION ALL
   SELECT c."conversationId", r.idx + 1
   FROM   rcte                r
   JOIN   "conversationUsers" c USING ("conversationId")
   WHERE  c."userId" = $1[idx + 1]
   )
SELECT "conversationId"
FROM   rcte r
WHERE  idx = array_length($1, 1)
AND    NOT EXISTS (
   SELECT FROM "conversationUsers"
   WHERE  "conversationId" = r."conversationId"
   AND    "userId" <> ALL($1);

Telepon:

EXECUTE conversations('{1,4,6}');

db<>fiddle di sini (juga mendemonstrasikan fungsi )

Masih ada ruang untuk perbaikan:untuk mendapatkan top kinerja Anda harus menempatkan pengguna dengan percakapan paling sedikit terlebih dahulu di array input Anda untuk menghilangkan baris sebanyak mungkin lebih awal. Untuk mendapatkan performa terbaik, Anda dapat membuat kueri non-dinamis dan non-rekursif secara dinamis (menggunakan salah satu dari cepat teknik dari tautan pertama) dan jalankan itu secara bergantian. Anda bahkan dapat membungkusnya dalam satu fungsi plpgsql dengan SQL dinamis ...

Penjelasan lebih lanjut:

Alternatif:MV untuk tabel yang jarang ditulis

Jika tabel "conversationUsers" sebagian besar hanya-baca (percakapan lama tidak mungkin berubah) Anda dapat menggunakan MATERIALIZED VIEW dengan pengguna pra-agregasi dalam array yang diurutkan dan buat indeks btree biasa pada kolom array itu.

CREATE MATERIALIZED VIEW mv_conversation_users AS
SELECT "conversationId", array_agg("userId") AS users  -- sorted array
FROM (
   SELECT "conversationId", "userId"
   FROM   "conversationUsers"
   ORDER  BY 1, 2
   ) sub
GROUP  BY 1
ORDER  BY 1;

CREATE INDEX ON mv_conversation_users (users) INCLUDE ("conversationId");

Indeks penutup yang ditunjukkan membutuhkan Postgres 11. Lihat:

Tentang menyortir baris dalam subkueri:

Dalam versi yang lebih lama, gunakan indeks multikolom biasa pada (users, "conversationId") . Dengan array yang sangat panjang, indeks hash mungkin masuk akal di Postgres 10 atau yang lebih baru.

Maka kueri yang jauh lebih cepat adalah:

SELECT "conversationId"
FROM   mv_conversation_users c
WHERE  users = '{1,4,6}'::int[];  -- sorted array!

db<>fiddle di sini

Anda harus mempertimbangkan biaya tambahan untuk penyimpanan, penulisan, dan pemeliharaan dengan manfaat untuk membaca kinerja.

Selain:pertimbangkan pengidentifikasi hukum tanpa tanda kutip ganda. conversation_id bukannya "conversationId" dll.:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Penyetelan Kinerja Rel untuk Produksi?

  2. cara menggabungkan CTE rekursif dan CTE normal

  3. mengatur hstore di Rails4, kunci/nilai dinamis

  4. Ikhtisar Pemrosesan VACUUM di PostgreSQL

  5. Ekspor ke CSV dan Kompres dengan GZIP di postgres