Select column values linked to exact set of values in another column
-
01-10-2020 - |
Вопрос
Given the below chats_users
table in Postgres 9.5, with integer columns chat_id
and user_id
, I want to find the chat_id
associated with an exact set of user_id
s, for example:
user_id IN (1, 3) => chat_id 2 user_id IN (1, 2, 3) => chat_id 1 user_id IN (1, 2) => no result
Is there a simple join to return the correct chat_id
?
|-----------------------|
| chats_users |
|-----------|-----------|
| chat_id 1 | user_id 1 |
| chat_id 1 | user_id 2 |
| chat_id 1 | user_id 3 |
| chat_id 2 | user_id 1 |
| chat_id 2 | user_id 3 |
| chat_id 3 | user_id 2 |
| chat_id 3 | user_id 3 |
|-----------------------|
Решение
Assuming a PRIMARY KEY
on (chat_id, user_id)
- or a UNIQUE
constraint plus NOT NULL
on both columns.
To allow any number of IDs, use a 1-dimensional array parameter:
SELECT chat_id
FROM chats_users c
WHERE user_id = ANY ('{1,2,3}'::int[])
GROUP BY 1
HAVING count(*) = array_length('{1,2,3}'::int[], 1)
AND NOT EXISTS (
SELECT 1
FROM chats_users
WHERE chat_id = c.chat_id
AND user_id <> ALL ('{1,2,3}'::int[])
);
Typically, in function or prepared statements, you provide the array parameter only once. Example for prepared statement:
PREPARE foo(int[]) AS
SELECT chat_id
FROM chats_users c
WHERE user_id = ANY ($1)
GROUP BY 1
HAVING count(*) = array_length($1, 1) -- all required user_id
AND NOT EXISTS (
SELECT 1
FROM chats_users
WHERE chat_id = c.chat_id
AND user_id <> ALL ($1) -- no additional user_id
);
Tests:
EXECUTE foo('{1,2,3}'); -- 1
EXECUTE foo('{1,3}'); -- 2
EXECUTE foo('{2,3}'); -- 3
EXECUTE foo('{1,2}'); -- empty
db<>fiddle here
Old sqlfiddle.
That's basically the first solution by Martin in this related answer on SO (with some modifications to fit your case):
There are faster solutions for a small, given number of IDs.