Det här är ett fall av relational-division - med det extra speciella kravet att samma konversation inte ska ha några ytterligare användare.
Förutsatt är PK för tabellen "conversationUsers"
som tvingar fram unika kombinationer, NOT NULL
och tillhandahåller även det index som är väsentligt för prestanda implicit. Kolumner i flerkolumnen PK i denna ordning! Annars måste du göra mer.
Om ordningen på indexkolumnerna:
För den grundläggande frågan finns "brute force" metod för att räkna antalet matchande användare för alla konversationer med alla givna användare och filtrera sedan de som matchar alla givna användare. OK för små tabeller och/eller bara korta inmatningsmatriser och/eller få konversationer per användare, men skalar inte bra :
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[])
);
Eliminera konversationer med ytterligare användare med en NOT EXISTS
anti-semi-join. Mer:
Alternativa tekniker:
Det finns flera andra, (mycket) snabbare relational-division frågetekniker. Men de snabbaste är inte väl lämpade för en dynamik antal användar-ID.
För en snabb fråga som också kan hantera ett dynamiskt antal användar-ID:n, överväg en rekursiv CTE :
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[])
);
För enkel användning linda in detta i en funktion eller förberedd uttalande . Gilla:
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);
Ring:
EXECUTE conversations('{1,4,6}');
db<>fiol här (som också demonstrerar en funktion )
Det finns fortfarande utrymme för förbättringar:att nå topp prestanda måste du sätta användare med minsta möjliga konversationer först i din inmatningsuppsättning för att eliminera så många rader som möjligt tidigt. För att få högsta prestanda kan du generera en icke-dynamisk, icke-rekursiv fråga dynamiskt (med en av de snabba tekniker från den första länken) och exekvera det i sin tur. Du kan till och med slå in den i en enda plpgsql-funktion med dynamisk SQL ...
Mer förklaring:
Alternativ:MV för sparsamt skriven tabell
Om tabellen "conversationUsers"
är oftast skrivskyddad (gamla konversationer kommer sannolikt inte att ändras) kan du använda en MATERIALIZED VIEW
med föraggregerade användare i sorterade arrayer och skapa ett vanligt btree-index på den arraykolumnen.
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");
Det visade täckningsindexet kräver Postgres 11. Se:
Om sortering av rader i en underfråga:
I äldre versioner använd ett vanligt index med flera kolumner på (users, "conversationId")
. Med mycket långa arrayer kan ett hashindex vara vettigt i Postgres 10 eller senare.
Då skulle den mycket snabbare frågan helt enkelt vara:
SELECT "conversationId"
FROM mv_conversation_users c
WHERE users = '{1,4,6}'::int[]; -- sorted array!
db<>fiol här
Du måste väga extra kostnader för lagring, skrivning och underhåll mot fördelarna för att läsa prestanda.
Bortsett från:överväg juridiska identifierare utan dubbla citattecken. conversation_id
istället för "conversationId"
etc.: