sql >> Database teknologi >  >> RDS >> PostgreSQL

SQL-forespørgsel for at finde en række med et bestemt antal tilknytninger

Dette er et tilfælde af - 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 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.:



  1. Kør SQL-forespørgsel ved opstart af MySQL-tjeneste

  2. Kan ikke finde 'libpq-fe.h headeren, når du forsøger at installere pg gem

  3. sammenligne datasæt og returnere det bedste match

  4. Docker mysql kan ikke oprette forbindelse til container