mysqlsqlansi-sql

MySQL: Grouping from inside INNER JOINs


MySQL 5.6 here. I have the following tables:

DESCRIBE profiles;
+----------------------------+---------------------+------+-----+---------+----------------+
| Field                      | Type                | Null | Key | Default | Extra          |
+----------------------------+---------------------+------+-----+---------+----------------+
| profile_id                 | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| profile_given_name         | varchar(100)        | YES  | MUL | NULL    |                |
| profile_surname            | varchar(100)        | YES  | MUL | NULL    |                |
+----------------------------+---------------------+------+-----+---------+----------------+

describe friendships;
+----------------------+---------------------+------+-----+---------+----------------+
| Field                | Type                | Null | Key | Default | Extra          |
+----------------------+---------------------+------+-----+---------+----------------+
| friendship_id        | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| requester_profile_id | bigint(20) unsigned | NO   | MUL | NULL    |                |
| recipient_profile_id | bigint(20) unsigned | NO   | MUL | NULL    |                |
+----------------------+---------------------+------+-----+---------+----------------+

The profiles table represents users of a system, and friendships represents a many-to-many relationship of which users are friends with which other users. When a user/profile sends a "Friend Request" to a user, they are considered to be therequester in of the friendship. Conversely, those who receive Friend Requests from others are the recipients.

I'm trying to write a (seemingly) simple ANSI-compliant SELECT that tells me all the profiles a particular profile is friends with, regardless of whether they were the requester or the recipient of the friendship.

To set the DB up with some data:

INSERT INTO friendships (
  friendship_ref_id,
  requester_profile_id,
  recipient_profile_id
) VALUES (
  '01234',
  2,
  1
), (
  '67890',
  1,
  3  
), (
  '78901',
  1,
  4
), (
  '89012',
  2,
  3
), (
  '90123',
  3,
  4
);

Then my best attempt thus far:

SELECT      f.requester_profile_id,
            f.recipient_profile_id
FROM        profiles p
INNER JOIN  friendships f
ON (
    f.requester_profile_id = p.profile_id
    OR
    f.recipient_profile_id = p.profile_id
)
WHERE       p.profile_id = 1;

When I run this I get:

+----------------------+----------------------+
| requester_profile_id | recipient_profile_id |
+----------------------+----------------------+
|                    1 |                    2 |
|                    1 |                    3 |
|                    1 |                    4 |
|                    2 |                    1 |
+----------------------+----------------------+

But what I really want is a single column composed of all the DISTINCT profile IDs (requester and recipient alike) that profile_id = 1 is friends with, something like:

+----------------------+
|      profile_friends |
+----------------------+
|                    2 |
|                    3 |
|                    4 |
+----------------------+

...since profile_id = 1 is friends with profile_id IN { 2, 3, 4 }, etc.

What am I missing here to make the query work?


Solution

  • There are two options here.

    The first uses a CASE to decide which column to use for each result:

    SELECT     
       CASE WHEN f.requester_profile_id = p.profile_id THEN f.recipient_profile_id 
            ELSE f.requester_profile_id END as profile_friends
    FROM        profiles p
    INNER JOIN  friendships f
      /* Original ON clause would still work, but I prefer the briefer syntax */
    ON p.profile_id IN (f.requester_profile_id,f.recipient_profile_id)
    WHERE       p.profile_id = 1;
    

    The second UNIONs two SELECT statements together, where one checks requester and the other checks recipient:

    SELECT      f.recipient_profile_id as profile_friends
    FROM        profiles p
    INNER JOIN  friendships f
    ON f.requester_profile_id = p.profile_id
    WHERE       p.profile_id = 1
    
    UNION
    
    SELECT      f.requester_profile_id
    FROM        profiles p
    INNER JOIN  friendships f
    ON  f.recipient_profile_id = p.profile_id
    WHERE       p.profile_id = 1;