Most efficient way to find friends of friends, excluding friends - mysql

Let's say I have a table called friends, and for each friendship, I add two entries. For example, if users 1 and 2 are friends, we will have:
uid1 uid2
----------
1 2
2 1
My goal is to find friends of friends, exclding the friends. The following query gives me friends of friends (including friends):
SELECT f2.uid2 FROM friends f1, friends f2 WHERE f1.uid1='YOUR_ID' AND f1.uid2=f2.uid1 AND f2.uid2!='YOUR_ID'
My preference is not to use IN or NOT IN. Can you think of anything to supplement this query?

Add an outer join with your own friends so you can filter them out.
SELECT f2.uid2 FROM friends f1
JOIN friends f2 ON f1.uid2 = f2.uid1
LEFT JOIN friends f3 ON f3.uid2 = f2.uid2 AND f3.uid1 = 'YourID'
WHERE f1.uid1='YourID'
AND f2.uid2!='YourID'
AND f3.uid2 IS NULL
SQLFIDDLE

You can do it with an additional join and aggregation:
SELECT f2.uid2
FROM friends f1 join
friends f2
on f1.uid1 = 'YOUR_ID' AND f1.uid2 = f2.uid1 AND f2.uid2 <> 'YOUR_ID'` join
friends f3
on f2.uid2 = f3.fuid1
GROUP BY f2.uid2
HAVING sum(f3.uid2 = 'YOUR_ID') = 0;

Related

Get real friends from friendlist

I know there are plenty of results on this topic, but they didn't help me.
I have a friends table with user1 and user2.
A real friend is when user1 is friend with user2 and user2 is friends with user1.
A friend request is when user1 is friends with user2. It looks something like this:
user1 | user2
-------------
1 | 2
2 | 1
1 | 3
3 | 1
1 | 5
How could the query look to get the real friends of #1?
I tried this but it returned null:
SELECT user2 FROM friends WHERE user1 = 1 AND user2 = 1
Also how would the query look for the friend request?
SELECT a.user1 FROM friends AS a JOIN friends AS b
ON a.user2 = b.user1 AND a.user1 = b.user2
WHERE a.user2 = ?
Where ? denote the ID the the "original" user.
One way to get this is with a JOIN operation:
SELECT f.user2
FROM friends f
JOIN friends r
ON r.user1 = f.user2
AND r.user2 = f.user1
WHERE f.user1 = 1
Given that a "real friend" relationship is identified by the existence of two tuples, that is, a real friend relationship between 1 and n would be represented by two rows in the table: (1,n) and (n,1).
The predicates in the join condition limit the rows returned to those rows that have a matching "inverse" tuple.
NOTE: a JOIN operation usually performs better than an equivalent IN (subquery) or EXISTS (subquery) patterns, but that performance difference is negligible with small sets. It's with larger sets that the performance difference becomes noticeable.
An equivalent result can be returned (usually less efficiently) using an EXISTS predicate:
SELECT f.user2
FROM friends f
WHERE f.user1 = 1
AND EXISTS ( SELECT 1
FROM friends r
WHERE r.user1 = f.user2
AND r.user2 = f.user1
)
or an IN predicate:
SELECT f.user2
FROM friends f
WHERE f.user1 = 1
AND f.user2 IN ( SELECT r.user1
FROM friends r
WHERE r.user2 = f.user1
)
(If there's not a unique constraint on friends(user1,user2), then JOIN may return some duplicate rows which may not be returned by the other queries, but none of the queries guarantee that no duplicates are returned. If there's no unique constraint, and you don't want any duplicates returned, then you can either add a DISTINCT keyword after the SELECT at the beginning of any of those statements, -or- add a GROUP BY f.user2 at the end of any of those statements.
To make the result set more deterministic (i.e. return the same result each time the query is run), you could add an ORDER BY clause. (But it's not needed with the GROUP BY since MySQL implicitly does an ORDER BY on the GROUP BY expressions.)
FOLLOWUP
explain how I could bind this results with the name in the user table? thank you. And how do I get the "not real" friends?
To get the name from the user table, we just add a JOIN to the user table, assuming id is the primary key column, and the user1 and user2 columns are foreign keys to the user table...
SELECT f.user2
, u.name
FROM friends f
JOIN user u
ON u.id = f.user2
JOIN friends r
ON r.user1 = f.user2
AND r.user2 = f.user1
WHERE f.user1 = 1
A "not real" friends would be represented as a tuple (row in the table) (1,n) which does not have a corresponding inverse tuple (n,1). To find those rows we use an anti-join pattern, which is an OUTER join (return all rows from one side plus any matching rows), and then a predicate that excludes the rows where a match was found (checking for a NULL in a column that is guaranteed not to be null if there is a match is how we do that):
This will find all the (1,n) tuples where there isn't a matching (n,1):
SELECT f.user2
, u.name
FROM friends f
JOIN user u
ON u.id = f.user2
LEFT
JOIN friends r
ON r.user1 = f.user2
AND r.user2 = f.user1
WHERE r.user1 IS NULL
AND f.user1 = 1
We'd have to flip that around to get the other side, (n,1) rows which don't have a matching (1,n) row:
SELECT f.user1
FROM friends f
JOIN user u
ON u.id = f.user1
LEFT
JOIN friends r
ON r.user2 = f.user1
AND r.user1 = f.user2
WHERE r.user2 IS NULL
AND f.user2 = 1
SELECT user2 FROM friends
WHERE user1 = 1
AND user2 IN (SELECT user1 from friends where user2 = 1);

Finding mutual friend in one way relationship table

want mysql query for finding mutual friend between two friend but
I am maintain the friendship of user in one way relationship for ex.
first is users table
id name
1 abc
2 xyz
3 pqr
Now second table is friend
id user_id friend_id
1 1 2
2 1 3
3 2 3
Now here i can say that abc(id=1) is friend of xyz(id=2) now similar way the xyz is friend of abc but now i want to find mutual friend between abc(id=1) and xyz(id=2) that is pqr so I want mysql query for that.
REVISED
This query will consider the "one way" relationship of a row in the friend table to be a "two way" relationship. That is, it will consider a friend relationship: ('abc','xyz') to be equivalent to the inverse relationship: ('xyz','abc'). (NOTE: we don't have any guarantee that both rows won't appear in the table, so we need to be careful about that. The UNION operator conveniently eliminates duplicates for us.)
This query should satisfy the specification:
SELECT mf.id
, mf.name
FROM (
SELECT fr.user_id AS user_id
, fr.friend_id AS friend_id
FROM friend fr
JOIN users fru
ON fru.id = fr.user_id
WHERE fru.name IN ('abc','xyz')
UNION
SELECT fl.friend_id AS user_id
, fl.user_id AS friend_id
FROM friend fl
JOIN users flf
ON flf.id = fl.friend_id
WHERE flf.user IN ('abc','xyz')
) f
JOIN users mf
ON mf.id = f.friend_id
GROUP BY mf.id, mf.name
HAVING COUNT(1) = 2
ORDER BY mf.id, mf.name
SQL Fiddle here http://sqlfiddle.com/#!2/b23a5/2
A more detailed explanation of how we arrive at this is given below. The original queries below assumed that a row in the friend table represented a "one way" relationship, in that "'abc' ff 'xyz'" did not imply "'xyz' ff 'abc'". But additional comments from the OP hinted that this was not the case.
If there is a unique constraint on friend(user_id,friend_id), then one way to get the result would be to get all of the friends of each user, and get a count of rows for that friend. If the count is 2, then we know a particular friend_id appears for both user 'abc' and for 'xyz'
SELECT mf.id
, mf.name
FROM friend f
JOIN users uu
ON uu.id = f.user_id
JOIN users mf
ON mf.id = f.friend_id
WHERE uu.name IN ('abc','xyz')
GROUP BY mf.id, mf.name
HAVING COUNT(1) = 2
ORDER BY mf.id, mf.name
(This approach can also be extended to find a mutual friend of three or more users, by including more users in the IN list, and changing the value we compare the COUNT(1) to.
This isn't the only query that will return the specified resultset; there are other ways to get it as well.
Another way to get an equivalent result:
SELECT u.id
, u.name
FROM ( SELECT f1.friend_id
FROM friend f1
JOIN users u1
ON u1.id = f1.user_id
WHERE u1.name = 'abc'
) t1
JOIN ( SELECT f2.friend_id
FROM friend f2
JOIN users u2
ON u2.id = f2.user_id
WHERE u2.name = 'xyz'
) t2
ON t2.friend_id = t1.friend_id
JOIN users u
ON u.id = t1.friend_id
ORDER BY u.id, u.name
NOTES
These queries do not check whether user 'abc' is a friend of 'xyz' (the two user names specified in the WHERE clause). It is only finding the common friend of both 'abc' and 'xyz'.
FOLLOWUP
The queries above satisfy the specified requirements, and all the examples and test cases provided in the question.
Now it sounds as if you want a row in that relationship table to be considered a "two way" relationship rather than just a "one way" relationship. It sounds like you want to want to consider the friend relationship ('abc','xyz') equivalent to ('xyz','abc').
To get that, then all that needs to be done is to have the query create the inverse rows,, and that makes it easier to query. We just need to be careful that if both those rows ('abc','xyz') and ('xyz','abc') already exist, that we don't create duplicates of them when we invert them.
To create the inverse rows, we can use a query like this. (It's simpler to look at this when we don't have the JOIN to the users table, and we use just the id value:
SELECT fr.user_id
, fr.friend_id
FROM friend fr
WHERE fr.user_id IN (1,2)
UNION
SELECT fl.friend_id AS user_id
, fl.user_id AS friend_id
FROM friend fl
WHERE fl.friend_id IN (1,2)
It's simpler if we don't include the predicates on the user_id and friend_id table, but that could be a very large (and expensive) rowset to materialize.
try this:
given that you want to get the mutual friends of friends 1 & 2
select friend_id into #tbl1 from users where user_id = 1
select friend_id into #tbl2 from users where friend_id = 2
select id, name from users where id in(select friend_id from #tbl1 f1, #tbl2 f2 where f1.friend_id=f2.friend_id)

MYSQL Retrieve mutual friends for several users

I am creating a small social network in which when a user posts anything.I will get ids of his friends to whom he referred the post.Now the problem is that the users who will be referred can be between 1 or 10. So now suppose that a user posted a status and referred 6 friends.
Now i need to find mutual friends between the user who posted the status and the people referred one by one.Meaning that suppose user who posted the status has id 1 and the people who are referred have 2,3,4,5,6,7.
So what the query will do it will find mutual friends between 1 and 2 then 1 and 3 then 1 and 4 and so on till 7.So at the end i will be getting a result containing the mutual friends between the user who posted and the people who are referred(or tagged we can say).
I've two tables User and Friends
User Table: Friends Table:
-------------------------- -----------------------------------
UserID Name friendsid friendname userid
-------------------------- -----------------------------------
1097517748 User1 3 friend1 536772587
100003052455644 User2 8 friend2 100003052455644
536772587 User3 8 friend3 1097517748
4 User4 3 friend4 100003052455644
Now the friends table can contain different people who does or does not exist in the user table.In this example user 2 and 3 has friend with id 8 in common.I've got my guy but that guy id i.e 8 must be present in the user table also.This is only between userid 2 and 3.
I will be getting several userid and i've find mutual friends of all the friends that are referred with the guy who has posted the status.
I hope I've cleared it.If I get the idea for the query I can generate it dynamically using php for different number of referred friends.
So far I've written this query.
SELECT count(f1.friendsid) FROM friends AS f1
INNER JOIN friends AS f2 ON f1.friendsid = f2.friendsid
WHERE f1.userid = 100003052455644
AND f2.userid = 1097517748
But this is only bringing me mutual friends between two user.When I add a third guy lets say f3 and compare it with f1 it does not bring any result and another problem with this query is that it is not verifying that the mutual friend id exists in the user table or not.After adding the 3rd guy.
SELECT count(f1.friendsid) FROM friends AS f1
INNER JOIN friends AS f2 ON f1.friendsid = f2.friendsid
INNER JOIN friends AS f3 ON f1.friendsid = f3.friendsid
WHERE f1.userid = 100003052455644
AND f2.userid = 1097517748
AND f3.userid = 536772587
This query does not bring any results.So I wanted to create a query that does all this.
EDIT:
Actually I am using facebook connect and I save all the users friends in my database when users login, the user table will contain only those users who are registered on my website.
I finally figured it out. The query was
SELECT DISTINCT(friendsid) FROM (
SELECT DISTINCT(f1.friendsid) FROM friends AS f1
INNER JOIN friends AS f2 ON f1.friendsid=f2.friendsid
and f1.userid = 100003052455644 and f2.userid =1097517748
INNER JOIN user ON f2.friendsid = user.userid and f1.friendsid = user.userid
UNION ALL
SELECT (f1.friendsid) FROM friends AS f1
INNER JOIN friends AS f2 ON f1.friendsid=f2.friendsid
and f1.userid = 100003052455644 and f2.userid =536772587
INNER JOIN user ON f1.friendsid = user.userid and f2.friendsid = user.userid
UNION ALL
SELECT DISTINCT(f1.friendsid) FROM friends AS f1
INNER JOIN friends AS f2 ON f1.friendsid=f2.friendsid
and f1.userid = 100003052455644 and f2.userid =694250830
INNER JOIN user ON f1.friendsid = user.userid and f2.friendsid = user.userid
) a
This query will find the id's of all the mutual friends between the user who posted the status and the users whom he refered.
The user with id 100003052455644 is the person who posted status and all the others are his friends whom he referred.The DISTINCT will make sure that the id's are unique and not repeated.This is for 3 persons he referred.We can add the UNION ALL part dynamically setting the id's and this will help us create a dynamic query for retrieving mutual friends for different number of referred people.

Mysql Query Help for Recommended friends

I have to find friends of friends (not my friends) from an sql table but the problem is that am stuck with excluding users who are already my friends or some blocked friends.
Here is the query
SELECT
IF(Friends.User_Id1 IN (1111,2222),
Friends.User_Id2,
Friends.User_Id1) AS 'Friend_Id',
CONCAT(User_FirstName," ",User_LastName) AS User_FirstName,
User_ProfilePic
FROM Users
JOIN Friends ON
IF(Friends.User_Id1 IN (1111,2222),
Friends.User_Id2,
Friends.User_Id1) = Users.User_Id
WHERE
(Friends.User_Id2 IN (1111,2222) OR Friends.User_Id1 IN (1111,2222)) AND
(Friends.User_Id2 != MY_ID AND Friends.User_Id1 != MY_ID AND Friends.Status = 1)
LIMIT 10;
In the above case, 1111 and 2222 are my friends and I am trying to get all their friends that is fine but what I want is:
users already my friends who are also friends with either 1111 and 2222 and are shown in the list. I don't want them here because they are in another friends list already.
Users I had blocked i.e Friends.Status for MY_ID and friends_friend_id = 3, I am having one in the case too, a user id 3333 is friend of 2222 and I had blocked him already but he is present in the list.
Please guide me if the searching via IN(1111,2222) would lead to some issue in future too because friends count would definitely increase. I have a comma separated list of my friends using group_concat before the above query. All this is in a stored procedure.
I hope I explained the problem clearly.
The first thing is that you should not implement my first answer. Instead, you should change or constrain your schema. See below for my suggestions.
As I understand your schema, it is:
create table Friends (
user_Id1 int,
user_Id2 int,
status int);
Where anytime there is a friend relationship, one of the id's is in position 1 and 1 is in position 2.
Now, assuming that my id is 1212, my list of friend's ids is:
select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1;
The list of my Friends' Friends' Ids is:
select f1.user_id1 as friends_of_friends
from Friends f1
where f1.user_Id2 in (select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1)
union
select user_id2 as friends_of_friends
from Friends f1
where f1.user_Id1 in (
select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id1 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1);
And then to add exclusions for my own friends and friends I've blocked, this becomes:
select f1.user_id1 as friends_of_friends
from Friends f1
where f1.user_Id2 in (select user_Id1 as my_friends_userId /* sub-query for friends of friends */
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1)
and f1.user_id1 not in /* exclusion of my own friends */
(select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1
)
and f1.user_id1 != '1212' /* exclusion of myself. */
and f1.user_id1 not in (select user_Id1 as my_friends_userId /* exlusion of people I've blocked. */
from Friends f
where f.user_Id2 = '1212'
and status = 3
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 3
)
union /* Now do it all over again for user_id2 */
select f2.user_id2 as friends_of_friends
from Friends f2
where f2.user_Id1 in (select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1)
and f2.user_id2 not in
(select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1
)
and f2.user_id2 != '1212'
and f2.user_id2 not in (select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 3
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 3
)
where I've marked the first time for each of these conditions. Now, you can see the mess of union's I had to do for this. (Which should probably be union distinct)
You should not be creating the in-clause with a group-concat. Despite the length here, it's faster.
You can ask what the individual pieces do. But again, my advise is DON'T DO THIS. This is why good table design up front makes things a lot easier.
SQL Fiddle for reference and to show results: http://sqlfiddle.com/#!2/e6376/13
EDIT: Just to add about how I would change this schema.
It's unclear if in your app, relationships between friends is a Google one (asymmetric relationships allowed), or a Facebook one (only symmetric relationships allowed).
In both cases, I'd change the schema to be:
create table Friends (
individual_userId int,
friend_userId int,
status int);
In the Google, case, you're done. In the Facebook case, I'd use this structure, but require that for every relationship, two rows go into the table. So if '1212' is Facebook friends w/ '0415', then there are (individual_userid, friend_userId) rows of ('1212', '0415') & ('0415','1212'). Ensuring that this works and is maintained would require stored procedures for insertions/deletions to make sure both rows are added and deleted. (There's no update -- these are unique ids.)
If we're sure that these relationships are maintained and that the friend initiating a relationship is always present in individual_userId, then, my final query becomes:
select f1.friend_userId as friends_of_friends
from Friends f1
where f1.individual_userId in ( /* retrieve my friend list */
select friend_userId as my_friends_userId
from Friends f
where f.individual_userId = '1212'
and status = 1)
and f1.friend_userId not in ( /* exclusion of my own friends */
select friend_userId as my_friends_userId
from Friends f
where f.individual_userId = '1212'
and status = 1
)
and f1.friend_userId not in ( /* exlusion of people I have blocked. */
select friend_userId as my_friends_userId
from Friends f
where f.individual_userId = '1212'
and status = 3
)
and f1.friend_userId != '1212' /* exclusion of myself. */
which is much easier to deal with. You could also rewrite this as a series of joins instead, but I suspect that as a first step, using in and not in clauses like this is easier to read.
Revised sqlfiddle: http://sqlfiddle.com/#!2/92ff2/1
(I'd have to test it with a large data set, but my gut says that the joins will be faster -- but for code like this, I suspect learning/getting the right answer is more important than optimizing initially for speed.)
As Mike's answer interprets, and so do I, your table structure is something like
create table Friends (
user_Id1 int,
user_Id2 int,
status int);
What you appear to want is a DISTINCT list of friends that are either friends of yours OR friends DIRECTLY ASSOCIATED with your friends (ie: 1 degree of separation from you). So, lets go with this scenario.
You are person ID 1111 and have friends 2222 and 3333.
Person 2222 has friends of 1111 (you), 3333 (your other friend) and 4444 (new person).
Person 3333 has friends of 1111 (you), 4444 (same as person 3333's friend -- coincidental), and 5555.
Now, 2nd degree of separation (not what you are looking for) is that person 4444 has friend of 6666, 7777, 8888. You don't care about these others (6666, 7777, 8888)
You are looking for the entire list of friends that are not you, and just want to see
Friends 2222, 3333, 4444, 5555.
I would start with a list of just your friends and use that as a basis to get THEIR friends. The inner-most query is to just get your distinct friends (without hard-coding who YOUR friends are). Then from that, get all their friends. If they happen to have similar, the "DISTINCT" will filter that out for you. After those are selected, get a UNION of your direct friends to represent "AllFriends". By using the IF(), we want whoever the "other" person is based on the join qualification. If the person joined on is in position 1, then we want the OTHER person, and vice-versa. Once you have your distinct list of friends, THEN join that to the users table for getting their name, picture and any other profile info.
select
AllFriends.FinalFriendID,
CONCAT(U.User_FirstName, " ", U.User_LastName) AS User_FirstName,
U.User_ProfilePic
from
( select DISTINCT
IF( F2.User_ID1 = YourDirectFriends.PrimaryFriend, F2.User_ID2, F2.User_ID1 )
as FinalFriendID
from
( select DISTINCT
IF( F.User_ID1 = YourID, F.User_ID2, F.User_ID1 ) as PrimaryFriendID
from
Friends F
where
F.user_ID1 = YourID
OR F.User_ID2 = YourID ) YourDirectFriends
JOIN Friends F2
ON YourDirectFriends.PrimaryFriendID = F2.User_ID1
OR YourDirectFriends.PrimaryFriendID = F2.User_ID2
UNION
select DISTINCT
IF( F.User_ID1 = YourID, F.User_ID2, F.User_ID1 ) as FinalFriendID
from
Friends F
where
F.user_ID1 = YourID
OR F.User_ID2 = YourID ) ) as AllFriends
JOIN Users U
on AllFriends.FinalFriendID = U.User_ID
Oh yeah, add in your qualifier of "Status" where applicable, and your limit requirement.

Friendfeed showing same relationship twice

I was building a feed system ala facebook just for the fun and training of it but I came up with an interesting problem. Imagine this scenario:
I am friend with nr1, nr2 and nr3. In my database it is also a user, nr4 in the database who I'm not friend with. So let say nr1 befriend nr4, in that case I want my feed to get updated with nr1 is friend with nr4. This is basic not very hard to do (at least to get it working, I don't no s*** about performance :P).
Then nr1 befriend nr2 but this is equivalent with nr2 befriend nr1 and that's my problem.
My friendship database works in the way that one friendship generates two records. I've read that's usually the faster way to do it.
Friendship table:
user_id
friend_id
pending
lastchange
User table:
user_id
user_name
I've came up with the following code:
SELECT
u1.user_id AS friend_id,
u1.user_name SA friend_name,
u2.user_id AS friends_friend_id,
u2.user_name AS friends_friend_name
FROM users AS u1
JOIN friendship AS f1
ON u1.user_id = f1.friend_id
&& f1.user_id = {$user_id}
&& f1.friend_pending = 0
JOIN friendship AS f2
ON u1.user_id = f2.user_id
&& f2.friend_pending = 0
JOIN users AS u2
ON f2.friend_id = u2.user_id
WHERE TIMESTAMPDIFF(SECOND, f2.lastchange, '{$last_login}' ) < 0;
Okey what this basically does is that it get all the users I'm friend with who befriend other people since my last login. The only problem is that if I'm friend with two people who befriend each other both of them will be selected in this query.
The difference (which makes it impossible to use SELECT DISTINCT) is that;
the first time nr1 will be the friend and nr 2 the friends friend and
the second time nr2 will be the friend and nr1 will be the friends friend.
I don't want that, I want one row per relationship.
You guys have any idea how to solve this? Any solution I came up with has ended with nothing being displayed :(
Thanks!
1st edit: I just came up with another problem which also is connected to this problem. In phpMyAdmin the above generate a correct table with the 4 columns. But in php (or rather codeigniter's query function) it only show two columns but adds more rows... this end up in I have no idea who befriend who..
2nd edit again: Okey I just solved the problem I presented in the first edit, I simply renamed the columns with AS. Still having the primary problem thou.
3rd edit: Stevie G was requesting some sample data, this is query result:
friend_id | friend_name | friends_friend_id | friends_friend_name
1 nr1 4 nr4
1 nr1 2 nr2
2 nr1 1 nr2
This is what I get if you follow my little story. the id = 1 is not "me", it's another user
4th edit Unfortunately I wasn't quite clear enough I think. What I want to get in the end is an array where each element contains:
friend_id
friend_name
friends_friend_id
friends_friend_name
Just like the sample data from edit 3 except the names I had before might have mislead you..
So my problem is, as you can see edit 3, that if two of my friends became friends I will get two rows for one relationship. What I want to get would be:
friend_id | friend_name | friends_friend_id | friends_friend_name
1 nr1 2 nr2
2 nr1 1 nr2
I hope I made myself clearer! Thanks
So to me it looks like your requirements are incomplete, in that in the friend feed, if nr2 and nr1 befriend each other, do you want to:
See something that says "Your mutual friends nr2 and nr1 befriended each other!"
See something that says "Your friend nr2 befriended nr1!" based on who actually reached out for friendship first.
Scenario #1
You need another join checking if your friends' friends are also in your friends and if so, returning a boolean column named AlsoMyFriend. Then if AlsoMyFriend is 1, return a "Your mutual friends befriended each other", otherwise return a normal "Your friend befriended someone new."
Alternatively, you could do this in code, by simply checking to see if your friends' friends' ID is in your array of friends' IDs and flipping your feed statement based on that.
Scenario #2
Add a column to the friendship table *primary_mover* and set that value to the *user_id* that initiated the friendship. When looking up friends' friends filter for *where primary_mover = user_id* to only get 1 of the 2-sided relationship.
As a simple measure, you could simply add a filter to say only pull friends' friends where the friends' friend ID is greater than my friends' ID:
SELECT
u1.user_id AS friend_id,
u1.user_name SA friend_name,
u2.user_id AS friends_friend_id,
u2.user_name AS friends_friend_name
FROM users AS u1
JOIN friendship AS f1
ON u1.user_id = f1.friend_id
&& f1.user_id = {$user_id}
&& f1.friend_pending = 0
JOIN friendship AS f2
ON u1.user_id = f2.user_id
&& f2.friend_pending = 0
&& f2.friend_id > f2.user_id -- pulls nr1 befriending nr2, not nr2 befriending nr1
JOIN users AS u2
ON f2.friend_id = u2.user_id
WHERE TIMESTAMPDIFF(SECOND, f2.lastchange, '{$last_login}' ) < 0;
Did you try using
SELECT DISTINCT
This is a tricky one do do in your head, but, try this:
SELECT --Select your user details, and your direct friends details
u1.user_id UserId,
u1.user_name UserName,
u2.user_id FriendsUserId,
u2.user_name FriendsUserName
FROM users AS u1
JOIN friendship AS f1
ON u1.user_id = {$user_id}
&& f1.friend_pending = 0
JOIN users AS u2
ON f1.friend_id = u2.user_id
JOIN friendship AS f2
ON f1.friend_id = f2.userid
&& f2.friend_pending = 0
WHERE TIMESTAMPDIFF(SECOND, f2.lastchange, '{$last_login}' ) < 0;
UNION
SELECT --Select your user details, and your friends friends details
u3.user_id UserId,
u3.user_name UserName,
u4.user_id FriendsUserId,
u4.user_name FriendsUserName
FROM users AS u3
JOIN friendship AS f3
ON u3.user_id = {$user_id}
&& f3.friend_pending = 0
JOIN friendship AS f4
ON f3.friend_id = f4.user_id
&& f4.friend_pending = 0
JOIN users AS u4
ON f3.friend_id = u4.user_id
WHERE TIMESTAMPDIFF(SECOND, f4.lastchange, '{$last_login}' ) < 0;
Assuming the results are as I think they are, the UNION should filter out the dupes as u1.user_id should always be your user_id..
Try
SELECT
u1.user_id AS friend_id,
u1.user_name SA friend_name,
u2.user_id AS friends_friend_id,
u2.user_name AS friends_friend_name
FROM users AS u1
JOIN friendship AS f1
ON u1.user_id = f1.friend_id
&& f1.user_id = {$user_id}
&& f1.friend_pending = 0
JOIN friendship AS f2
ON u1.user_id = f2.user_id
&& f1.user_id <> f2.friend_id
&& f2.friend_pending = 0
JOIN users AS u2
ON f2.friend_id = u2.user_id
WHERE TIMESTAMPDIFF(SECOND, f2.lastchange, '{$last_login}' ) < 0;