jambags
jambags

Reputation: 33

SELECTing rows where no other rows match

This seemed pretty simple to start with, but it's getting awkward.

Suppose we have a table containing...

+---------+-----------+
| chat_id | friend_id |
+---------+-----------+
| A       |         1 |
| A       |         2 |
| A       |         3 |
| B       |         1 |
| B       |         2 |
| C       |         1 |
| C       |         2 |
| C       |         3 |
| D       |         1 |
| D       |         2 |
| D       |         3 |
| D       |         4 |
| D       |         5 |
| E       |         0 |
| E       |         1 |
| E       |         2 |
| E       |         3 |
| E       |         4 |
| E       |         5 |
| E       |         6 |
| E       |         7 |
| F       |         0 |
| F       |         1 |
| G       |         1 |
| G       |         2 |
+---------+-----------+

And I wish to select only those chat_id that have friend_ids 1 and 2 and no other friend_id, what would the SQL be to get B and G returned?

So far, the best I've come up with is:

SELECT DISTINCT a.chat_id, COUNT(*) 
FROM tt2 a 
LEFT JOIN tt2 b 
ON a.chat_id = b.chat_id 
AND b.friend_id NOT IN (1,2) 
WHERE a.friend_id in (1,2) 
and b.chat_id IS NULL GROUP BY a.chat_id HAVING COUNT(*) = 2;

+---------+----------+
| chat_id | count(*) |
+---------+----------+
| B       |        2 |
| G       |        2 |
+---------+----------+
2 rows in set (0.00 sec)

And just in case I was looking for chat_id where only 1,2,3 exist...

SELECT DISTINCT a.chat_id, COUNT(*) 
FROM tt2 a 
LEFT JOIN tt2 b 
ON a.chat_id = b.chat_id 
AND b.friend_id not in (1,2,3) 
WHERE a.friend_id IN (1,2,3) 
AND b.chat_id IS NULL 
GROUP BY a.chat_id 
HAVING COUNT (*) = 3;

+---------+----------+
| chat_id | count(*) |
+---------+----------+
| A       |        3 |
| C       |        3 |
+---------+----------+

But this table could get massive and I need the SQL to be swift, does anyone know a better way?

To try and clarify... I get given a bunch of friend_id's and I want to get chat_id where only those friend_id exist for that chat_id.... with the SQL being quick (on sqlite)

Many thanks in advance!

Upvotes: 3

Views: 75

Answers (2)

Here's an option that should be able to limit the amount of data needed

SELECT 
    d.chat_id,
    COUNT(DISTINCT s.friend_id) AS matchedFriends,
    COUNT(DISTINCT d.friend_id) AS totalFriends
FROM tt2 AS d
INNER JOIN tt2 AS s
    ON s.chat_id = d.chat_id
    AND s.friend_id IN (1,2)
GROUP BY d.chat_id
HAVING matchedFriends = 2
AND totalFriends = matchedFriends

The INNER JOIN s makes sure that it only hits rows that have got at least one of the requested friends in. The matchedFriends count checks how many of the requested friends are found.

The totalFriends count then checks how many friends in total are on that chat.

Finally the HAVING first makes sure there are 2 matched friends, and then checks the number of friends in total equals the number of matched friends.

This will require you to supply both a list of friends, and a number of friends you are looking for, but should be efficient.

For increased efficiency, have an index on (chat_id,friend_id) (if you don't already, assuming it's a 2-part PK at time of writing)

Upvotes: 1

Aziz Shaikh
Aziz Shaikh

Reputation: 16524

Try this:

SELECT chat_id, GROUP_CONCAT(DISTINCT friend_id ORDER BY friend_id) AS friends 
FROM table_1
GROUP BY chat_id
HAVING friends = '1,2'

Note: This works in mysql but I doubt that it will work on sqlite.

Upvotes: 0

Related Questions