Dette er et tilfælde af relational-division - med det tilføjede særlige krav, at den samme samtale ikke må have nogen yderligere brugere.
Forudsat er PK for tabel "conversationUsers"
som håndhæver unikke kombinationer, IKKE NULL
og giver også implicit det indeks, der er afgørende for ydeevne. Kolonner af multikolonne PK i denne bestille! Ellers skal du gøre mere.
Om rækkefølgen af indekskolonner:
Til den grundlæggende forespørgsel er der "brute force" tilgang til at tælle antallet af matchende brugere for alle samtaler med alle givne brugere og filtrer derefter dem, der matcher alle givne brugere. OK for små tabeller og/eller kun korte input-arrays og/eller få samtaler pr. bruger, men skalerer ikke godt :
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[])
);
Eliminering af samtaler med yderligere brugere med en EKSISTERER IKKE anti-semi-sammenføjning. Mere:
Alternative teknikker:
Der er forskellige andre, (meget) hurtigere relational-division forespørgselsteknikker. Men de hurtigste er ikke velegnede til en dynamik antal bruger-id'er.
For en hurtig forespørgsel der også kan håndtere et dynamisk antal bruger-id'er, overvej 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[])
);
For at lette brugen skal dette indpakkes i en funktion eller forberedt erklæring . Ligesom:
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 til:
EXECUTE conversations('{1,4,6}');
db<>fiddle her (demonstrerer også en funktion )
Der er stadig plads til forbedringer:at komme top ydeevne skal du sætte brugere med færrest samtaler først i dit input-array for at eliminere så mange rækker som muligt tidligt. For at opnå den bedste ydeevne kan du generere en ikke-dynamisk, ikke-rekursiv forespørgsel dynamisk (ved at bruge en af de hurtige teknikker fra det første link) og udfør det igen. Du kan endda pakke det ind i en enkelt plpgsql-funktion med dynamisk SQL ...
Mere forklaring:
Alternativ:MV for tyndt skrevet tabel
Hvis tabellen "conversationUsers"
er for det meste skrivebeskyttet (det er usandsynligt, at gamle samtaler ændres), kan du bruge en MATERIALISERET VISNING
med præ-aggregerede brugere i sorterede arrays og opret et almindeligt btree-indeks på den array-kolonne.
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 påviste dækkende indeks kræver Postgres 11. Se:
Om sortering af rækker i en underforespørgsel:
I ældre versioner skal du bruge et almindeligt indeks med flere kolonner på (brugere, "conversationId")
. Med meget lange arrays kan et hash-indeks give mening i Postgres 10 eller nyere.
Så ville den meget hurtigere forespørgsel simpelthen være:
SELECT "conversationId"
FROM mv_conversation_users c
WHERE users = '{1,4,6}'::int[]; -- sorted array!
db<>fiddle her
Du skal afveje ekstra omkostninger til opbevaring, skrivning og vedligeholdelse mod fordele for at læse ydeevne.
Til side:Overvej juridiske identifikatorer uden dobbelte anførselstegn. samtale-id
i stedet for "conversationId"
osv.: