sql >> Databáze >  >> RDS >> PostgreSQL

SQL dotaz pro nalezení řádku s určitým počtem asociací

Toto je případ - s přidaným zvláštním požadavkem, že stejná konverzace nesmí obsahovat žádné další uživatelů.

Za předpokladu je PK tabulky "conversationUsers" který vynucuje jedinečnost kombinací, NOT NULL a také implicitně poskytuje index nezbytný pro výkon. Sloupce vícesloupcového PK v tomto objednat! Jinak musíte udělat více.
O pořadí sloupců indexu:

Pro základní dotaz existuje "hrubá síla" přístup k počítání počtu odpovídajících uživatelů pro všechny konverzace všech daných uživatelů a poté filtrovat ty, které odpovídají všem daným uživatelům. OK pro malé tabulky a/nebo pouze krátká vstupní pole a/nebo málo konverzací na uživatele, ale neškáluje se dobře :

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[])
   );

Eliminace konverzací s dalšími uživateli s NOT EXISTS anti-polo-spojení. Více:

Alternativní techniky:

Existují různé další, (mnohem) rychlejší dotazovací techniky. Ty nejrychlejší se ale pro dynamické moc nehodí počet uživatelských ID.

Pro rychlý dotaz který si také poradí s dynamickým počtem uživatelských ID, zvažte rekurzivní 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[])
   );

Pro snadné použití to zabalte do funkce nebo připraveného prohlášení . Jako:

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);

Volejte:

EXECUTE conversations('{1,4,6}');

db<>fiddle zde (také demonstruje funkci )

Stále je co zlepšovat:dostat se nahoru výkon musíte do vstupního pole umístit uživatele s nejmenším počtem konverzací jako první, abyste co nejdříve odstranili co nejvíce řádků. Chcete-li dosáhnout špičkového výkonu, můžete dynamicky generovat nedynamický, nerekurzivní dotaz (pomocí jednoho z rychlých techniky z prvního odkazu) a proveďte je postupně. Můžete to dokonce zabalit do jediné funkce plpgsql s dynamickým SQL ...

Další vysvětlení:

Alternativa:MV pro řídce psanou tabulku

Pokud je tabulka "conversationUsers" je většinou jen pro čtení (staré konverzace se pravděpodobně nezmění), můžete použít MATERIALIZED VIEW s předem agregovanými uživateli v seřazených polích a v tomto sloupci pole vytvořte prostý index btree.

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");

Demonstrovaný index pokrytí vyžaduje Postgres 11. Viz:

O řazení řádků v poddotazu:

Ve starších verzích používejte prostý vícesloupcový index na (users, "conversationId") . S velmi dlouhými poli může mít hash index smysl v Postgresu 10 nebo novějším.

Pak by mnohem rychlejší dotaz byl jednoduše:

SELECT "conversationId"
FROM   mv_conversation_users c
WHERE  users = '{1,4,6}'::int[];  -- sorted array!

db<>fiddle zde

Musíte zvážit dodatečné náklady na úložiště, zápisy a údržbu s přínosy pro výkon čtení.

Stranou:zvažte legální identifikátory bez uvozovek. conversation_id místo "conversationId" atd.:



  1. Nelze nainstalovat klenot mysql na Mac OS X

  2. mysql_connect podruhé nefunguje

  3. Seznam všech spouštěčů v databázi Oracle

  4. PHP MySQLi multi_query připravený příkaz