PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

SQL заявка за намиране на ред с определен брой асоциации

Това е случай на - с добавеното специално изискване същият разговор да няма допълнителни потребители.

Приемаме е PK на таблицата "conversationUsers" което налага уникалност на комбинациите, NOT NULL и също така имплицитно предоставя индекса, който е от съществено значение за производителността. Колони на многоколонния PK в това поръчка! В противен случай трябва да направите повече.
Относно реда на колоните на индекса:

За основната заявка има "груба сила" подход за преброяване на броя на съответстващите потребители за всички разговори на всички дадени потребители и след това филтрирайте тези, които съответстват на всички дадени потребители. ОК за малки таблици и/или само кратки входни масиви и/или няколко разговора на потребител, но не се мащабира добре :

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

Премахване на разговори с допълнителни потребители с NOT EXISTS анти-полусъединяване. Още:

Алтернативни техники:

Има различни други, (много) по-бързи техники за заявки. Но най-бързите не са подходящи за динамика брой потребителски идентификатори.

За бързо запитване който също може да работи с динамичен брой потребителски идентификатори, помислете за рекурсивен 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[])
   );

За по-лесна употреба обвийте това във функция или подготвен израз . Като:

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

Обаждане:

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

db<>fiddle тук (също демонстрира функция )

Все още има място за подобрение:да станете върх производителност, трябва да поставите потребители с най-малко разговори първи във вашия входен масив, за да елиминирате възможно най-много редове по-рано. За да постигнете най-добра производителност, можете динамично да генерирате нединамична, нерекурсивна заявка (като използвате един от бързите техники от първата връзка) и изпълнете това на свой ред. Можете дори да го обвиете в една функция plpgsql с динамичен SQL ...

Повече обяснение:

Алтернатива:MV за рядко написана таблица

Ако таблицата "conversationUsers" е предимно само за четене (старите разговори е малко вероятно да се променят) можете да използвате MATERIALIZED VIEW с предварително агрегирани потребители в сортирани масиви и създайте обикновен 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");

Демонстрираният индекс на покритие изисква Postgres 11. Вижте:

Относно сортирането на редове в подзаявка:

В по-стари версии използвайте обикновен индекс с много колони на (users, "conversationId") . При много дълги масиви хеш индексът може да има смисъл в Postgres 10 или по-нова версия.

Тогава много по-бързата заявка ще бъде просто:

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

db<>fiddle тук

Трябва да претеглите добавените разходи за съхранение, запис и поддръжка срещу ползите за производителност при четене.

Настрана:разгледайте легалните идентификатори без двойни кавички. conversation_id вместо "conversationId" и т.н.:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. PostgreSQL многомерни масиви

  2. Създайте правило или тригер на Postgres за автоматично преобразуване на колона в малки или главни букви при вмъкване

  3. postgresql, не можа да идентифицира колона в типа данни на запис

  4. JSONb дати:действителни дати вътрешно?

  5. Най-добрият начин за преброяване на записи на произволни интервали от време в Rails+Postgres