Mysql Query Help for Recommended friends - mysql

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.

Related

Get list of friends n depth

I have simple table friends that look like that:
With the id of a person (id_friend) and the id of its friend (id_friend_of).
I'm trying to get all the IDs of friends of a specific user with a depth, so get all people linked to a specific user with a determined depth.
What I'm trying for a depth of 2 (get the friends of the user and the friends of its friends) :
SELECT DISTINCT
a.id_friend_of
FROM friend a
JOIN friend b
ON b.id_friend = a.id_friend_of
WHERE a.id_friend = 1 AND
b.id_friend <> a.id_friend
But it's not working, I'm only getting the friends of the user but not the friends of friends.
What can I do to make this work?
get the friends of the user and the friends of its friends
You can get the friends of the user with a simple filtered query on the table and the friends of friends with a self join of the table.
Then use UNION to get the results of the 2 queries, which will also remove duplicates:
SELECT id_friend_of
FROM friend
WHERE id_friend = 1
UNION
SELECT f2.id_friend_of
FROM friend f1 INNER JOIN friend f2
ON f2.id_friend = f1.id_friend_of
WHERE f1.id_friend = 1 AND f2.id_friend_of <> 1
For levels above 2, it's better to use a recursive query (for MySql 8.0+):
WITH RECURSIVE cte AS (
SELECT *, 1 level
FROM friend
WHERE id_friend = 1
UNION ALL
SELECT f.*, level + 1
FROM cte c INNER JOIN friend f
ON f.id_friend = c.id_friend_of
WHERE f.id_friend_of <> 1 AND level < 2 -- for level = 2
)
SELECT DISTINCT id_friend_of
FROM cte
See a simplified demo.

Mutual friends sql

I've seen multiple SO posts on mutual friends but I've structured my friends table in my db so that there are no duplicates e.g. (1,2) and not (2,1)
Create Table Friends(
user1_id int,
user2_id int
);
and then a constraint to make sure user1 id is always smaller than user2 id e.g 4 < 5
Mutual friends sql with join (Mysql)
I see suggestions that to find mutual friends it can be found using a join, so this is what I have but I think it's wrong because if I count the data in my db with the actual result from the query I get different results
select f1.user1_id as user1, f2.user1_id as user2, count(f1.user2_id) as
mutual_count from Friends f1 JOIN Friends f2 ON
f1.user2_id = f2.user2_id AND f1.user1_id <> f2.user1_id GROUP BY
f1.user1_id, f2.user1_id order by mutual_count desc
There are three join scenarios that I can see.
1 -> 2 -> 3 (mutual friend id between other IDs)
2 -> 3 -> 1 (mutual friend id > other IDs)
2 -> 1 -> 3 (mutual friend id < other IDs)
This can be resolved with this predicate...
ON f1.user1_id IN (f2.user1_id, f2.user2_id)
OR f1.user2_id IN (f2.user1_id, f2.user2_id)
AND <not joining the row to Itself>
But that will totally mess up the optimiser's ability to use indexes.
So, I'd union multiple queries.
(pseudo code as I'm on a phone)
SELECT u1, u2, COUNT(*) FROM
(
SELECT f1.u1, f2.u2 FROM f1 INNER JOIN f2 ON f1.u2 = f2.u1 AND f1.u1 <> f2.u2
UNION ALL
SELECT f1.u1, f2.u1 FROM f1 INNER JOIN f2 ON f1.u2 = f2.u2 AND f1.u1 <> f2.u1
UNION ALL
SELECT f1.u2, f2.u2 FROM f1 INNER JOIN f2 ON f1.u1 = f2.u1 AND f1.u2 <> f2.u2
) all_combinations
GROUP BY u1, u2
Each individual query will then be able to fully utilise indexes. (Put one index on u1 and another index on u2)
The result should be less esoteric code (with fairly long CASE statements) and a much lower costed execution plan.

how to find friends of friends in mysql

Description :
I have a table of users as following
students
id ------ name
1 ------ John
2 ------ Sarah
3 ------ Peter
and friends table as
Buddies
person1 ------ person2
1 ------ 2
2 ------ 3
Now I want all the friends of 2 and all other my friends whose friends are not my friends.
E.g in "people you may know" we see people that are friends of our friends but are not our friends
I have successfully written the query to find all my friends but I am not sure how to find "friends of my friends" in one query
Is there a way to do it in one query ....
I am finding friends like this
select * from `students` join `buddy_circle` on
'$reg_no' = `person_1` and `stregno` = `person_2` or
'$reg_no' = `person_2` and `stregno` = `person_1`
where stregno is the id of student and buddy_circle is the friends table and $regno is the id of the user
Maybe this?
I have tested it only with your example data.
select name from students where id in (
select p2 from buddies where p1 in (
select p2 from buddies where p1=[serach_for_id]));
Join twice to get friends of friends:
select distinct name
from buddy_circle a
join buddy_circle b on b.p1 = a.p2
join students on id = b.p2
where a.p1 = $reg_no
Note the order of tables in the query is such that the where clause applies to the first named table and joined tables flow on from that, which gives maximum performance.
Try this
select * from students As s
join buddy_circle As b on s.id= b.person_2 or b.person_1 = s.id
Where id =2
This query should give you a list of all students what that have the same friend of 2.
If it work for you I will re-write it in a different format to make it even better
I guess this will work for you.
First I select Your friends in two first query.
Then I query the Buddies table (or Friends in this example) that they are friends of your friends
It covers all the situations:
/*select my friends in col2 while I'm in col1 */
SELECT p2 FROM Friends WHERE p1 = #MyID
UNION
/*select my friends in col1 while I'm in col 2 */
SELECT p1 FROM Friends WHERE P2 = #MyID
Union
/*
select my friend's friends which
my firends are in col1 and my firend's friends are in col2
*/
SELECT p2 FROM Friends
WHERE
p1 in (SELECT P2 FROM Friends where P1=#MyID)
union
SELECT p1 FROM Friends
WHERE
p2 in (SELECT P2 FROM Friends where P1=#MyID)
union
SELECT p1 FROM Friends
WHERE
p2 in (SELECT P1 FROM Friends where P2=#MyID)
union
SELECT p2 FROM Friends
WHERE
p1 in (SELECT P1 FROM Friends where P2=#MyID)

Most efficient way to find friends of friends, excluding friends

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;

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);