3

I'm building a social network, and I want my members to be able to easily find new friends. Just like in Facebook, I want to suggest them some people they may know by the number of mutual friends they have.

My PostgreSQL database structure for friendships is as following:

> PROFILES_FRIENDSHIPS
> ----------------------
> - fri_profile_from // int, Person who sent the friendship request
> - fri_profile_to // int, Person who received the friendship request
> - fri_accepted // tinyint, has the person accepted the friendship request?

This is the query I figured out in PostgreSQL for finding the number of mutual friends between 2 profiles (profile with ID 24, and profile with ID 26):

SELECT COUNT(*)
FROM profiles AS p
INNER JOIN (
        SELECT (
        CASE    WHEN ( 26 = f.fri_profile_from ) THEN f.fri_profile_to 
                    ELSE f.fri_profile_from END) AS fri_profile_from 
        FROM profiles_friendships AS f 
        WHERE 1 = 1
        AND (f.fri_profile_to = 26 OR f.fri_profile_from = 26) 
        AND fri_accepted = 1) 
AS f1 
ON (f1.fri_profile_from = p.pro_id) 
INNER JOIN (
        SELECT (
        CASE    WHEN ( 24 = f.fri_profile_from ) THEN f.fri_profile_to 
                    ELSE f.fri_profile_from END) AS fri_profile_from 
        FROM profiles_friendships AS f 
        WHERE 1 = 1
        AND (f.fri_profile_to = 24 OR f.fri_profile_from = 24) 
        AND fri_accepted = 1) 
AS f2 
ON (f2.fri_profile_from = p.pro_id)

Now I have been trying to convert this query to make it find the profiles with the most mutual friends of me, but who are not friends with me. But without success... I also researched a lot of examples on this website, but most them are working with double records in the friendships table. Like if 24 is friends with 26, there are 2 records: (24, 26) and (26, 24). That makes them easier to join and to find mutual friends, but that's not how I want to build my database.

If someone could help me to get started on this query, I would be very grateful.

wildplasser
  • 43,142
  • 8
  • 66
  • 109
user1026090
  • 458
  • 3
  • 9
  • 23

3 Answers3

2

Step 1 Get everyone that isnt a friend

Select * 
From profiles
where (from/to_friendship is not myID)

step 2 include a column with # of mutual friends and order by it

select *, 
  (select count(*) from [mutual friends query]) as NrOfMutualFriends)
From profiles
where (from/to_friendship is not myID)
Order by NrOfMutualFriends

Edit: Mutual Friends query:

Step 1 select all my friends and all his friends

select if(from = myId, to, from) as myfriendids
from PROFILES_FRIENDSHIPS where from = myid or to = myid

select if(from = hisId, to, from) as hisfriendids
from PROFILES_FRIENDSHIPS where from = hisId or to = hisId

Step 2 Combine these queries into 1

select count(*) 
from 
  ( select if(from = myId, to, from) as myfriendids
    from PROFILES_FRIENDSHIPS where from = myid or to = myid) myfriends
inner join 
  ( select if(from = hisId, to, from) as hisfriendids
  from PROFILES_FRIENDSHIPS where from = hisId or to = hisId) hisfriends
on myfriendsids = hisfriendsids
Alfons
  • 511
  • 4
  • 17
  • With [mutual friends query], you mean my query for finding the number of mutual friends between 2 ID's? But there I have my 2 ID's, I don't know how to include them in my new query.. – user1026090 Nov 05 '13 at 13:34
  • Added the selection of mutual friends – Alfons Nov 05 '13 at 13:47
2
WITH friends AS(
  SELECT p.pro_id, CASE WHEN f.fri_profile_from = p.pro_id THEN f.fri_profile_to 
                    ELSE f.fri_profile_from END AS friend_id
  FROM profiles
)
SELECT f2.pro_id, count(*) as friend_count
FROM friends AS f1
  JOIN friends AS f2
    ON f1.friend_id=f2.friend_id
       AND f1.pro_id != f2.pro_id
       AND f1.pro_id != f2.friend_id
WHERE f1.pro_id = :user_id
GROUP BY f2.pro_id
ORDER BY friend_count;
Jakub Kania
  • 15,665
  • 2
  • 37
  • 47
2

you can easily create inline view in double records format:

with cte_friends(user_id, friend_id) as (
    select
        fri_profile_from, fri_profile_to
    from PROFILES_FRIENDSHIPS
    where fri_accepted = 1

    union all -- or union if there could be duplicates

    select
        fri_profile_to, fri_profile_from
    from PROFILES_FRIENDSHIPS
    where fri_accepted = 1
)
select
    f2.friend_id, count(distinct f2.user_id)
from cte_friends as f1
    inner join cte_friends as f2 on f2.user_id = f1.friend_id
    left outer join cte_friends as f3 on f3.user_id = f2.friend_id and f3.friend_id = f1.user_id
where
    f1.user_id = 1 and f3.user_id is null and
    f2.friend_id != 1
group by f2.friend_id
order by 2 desc

sql fiddle demo

Roman Pekar
  • 107,110
  • 28
  • 195
  • 197