Това е случай на relational-division - с добавеното специално изискване същият разговор да няма допълнителни потребители.
Приемаме е 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
анти-полусъединяване. Още:
Алтернативни техники:
Има различни други, (много) по-бързи relational-division техники за заявки. Но най-бързите не са подходящи за динамика брой потребителски идентификатори.
За бързо запитване който също може да работи с динамичен брой потребителски идентификатори, помислете за рекурсивен 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"
и т.н.: