Ini adalah kasus relational-division - 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 relational-division 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.: