Вопрос

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_ids, 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с dba.stackexchange
scroll top