Reputation: 2094
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:
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.
Upvotes: 0
Views: 590
Reputation: 4374
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.)
Upvotes: 3
Reputation: 48149
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.
Upvotes: 1