Questo è un caso di relational-division - con l'aggiunta speciale requisito che la stessa conversazione non abbia aggiuntive utenti.
Supponendo è il PK della tabella "conversationUsers"
che impone l'unicità delle combinazioni, NOT NULL
e fornisce anche implicitamente l'indice essenziale per la performance. Colonne della PK multicolonna in questo ordine! Altrimenti devi fare di più.
Informazioni sull'ordine delle colonne dell'indice:
Per la query di base, c'è la "forza bruta" approccio per contare il numero di utenti corrispondenti per tutti conversazioni di tutti gli utenti dati e quindi filtrare quelle corrispondenti a tutti gli utenti indicati. OK per tabelle piccole e/o solo array di input brevi e/o poche conversazioni per utente, ma non scala bene :
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[])
);
Eliminazione delle conversazioni con utenti aggiuntivi con un NOT EXISTS
antisemiunione. Altro:
Tecniche alternative:
Ci sono vari altri, (molto) più veloci relational-division tecniche di interrogazione. Ma i più veloci non sono adatti a una dinamica numero di ID utente.
Per una interrogazione rapida che può anche gestire un numero dinamico di ID utente, considera un CTE ricorsivo :
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[])
);
Per facilità d'uso, avvolgilo in una funzione o in una dichiarazione preparata . Come:
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);
Chiama:
EXECUTE conversations('{1,4,6}');
db<>violino qui (mostrando anche una funzione )
C'è ancora spazio per migliorare:ottenere il top prestazioni è necessario inserire gli utenti con il minor numero di conversazioni al primo posto nell'array di input per eliminare il maggior numero possibile di righe in anticipo. Per ottenere le massime prestazioni puoi generare una query non dinamica e non ricorsiva in modo dinamico (utilizzando uno dei metodi veloci tecniche dal primo collegamento) ed eseguirlo a sua volta. Potresti persino avvolgerlo in una singola funzione plpgsql con SQL dinamico ...
Ulteriori spiegazioni:
Alternativa:MV per tabella scritta in modo sparso
Se la tabella "conversationUsers"
è per lo più di sola lettura (è improbabile che le vecchie conversazioni cambino) potresti usare un MATERIALIZED VIEW
con utenti preaggregati in array ordinati e crea un semplice indice btree su quella colonna dell'array.
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");
L'indice di copertura dimostrato richiede Postgres 11. Vedi:
Informazioni sull'ordinamento delle righe in una sottoquery:
Nelle versioni precedenti usa un semplice indice multicolonna su (users, "conversationId")
. Con array molto lunghi, un indice hash potrebbe avere senso in Postgres 10 o versioni successive.
Quindi la query molto più veloce sarebbe semplicemente:
SELECT "conversationId"
FROM mv_conversation_users c
WHERE users = '{1,4,6}'::int[]; -- sorted array!
db<>violino qui
Devi soppesare i costi aggiuntivi per l'archiviazione, le scritture e la manutenzione rispetto ai vantaggi in termini di prestazioni di lettura.
A parte:considera gli identificatori legali senza virgolette. conversation_id
invece di "conversationId"
ecc.: