Get real friends from friendlist - mysql

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

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.

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)

Single Row Friendship Database Schema with getting userid from one column or the other

I need help querying the friendID from a table.
My table stores the user id of two members who are friends together.
But in order to store a "friendhship" b/w two members I would have to store two records like this:
friendshipID | userID | friendID
1 | 5 | 10
2 | 10 | 5
Yet, that seems heavy for the DB when we really only need to store the first record as that is sufficient as it contains both ids of both members.
However, the trouble comes when I want to query the records of the friends of ID=5. Sometimes the ID is in the userID column and other times it is in the friendID column.
This is the query I am using:
SELECT *
FROM friends
WHERE userID = '5'
OR friendID = '5'
But what I want to do is something like this
SELECT
if $userID=5 then userID as myfriend
else friendID=5 then friendID as myfriend
FROM friends WHERE userID='5' OR myfriendID='5'
Hope that makes sense. In the end I would like to have all the friends ID's of member #5 and not bring up results with #5 as the friend or user....but just his friends.
This query would return the Id value, and name, of the friends of #5 as shown in this SQL Fiddle Example
SELECT f.FriendId AS FriendId
, u.Name AS FriendName
FROM FriendTable AS f
INNER JOIN UserAccount AS u ON f.FriendId = u.UserId
WHERE f.UserId = 5
UNION
SELECT f.UserId AS FriendId
, u.Name AS FriendName
FROM FriendTable AS f
INNER JOIN UserAccount AS u ON f.UserId = u.UserId
WHERE f.FriendId = 5
The UNION will remove duplicates, making this query work for both a single record of friends, or the 2 record friendship you mention in the comment. You shouldn't need the 2 record friendship though, because there is no new information being stored in the second record that you cannot get from only having one record.

Mysql, double join that use the same table twice, but indirectly

There are plenty of question about join on the same table, but I can't find something related to my problem
I have two tables:
user (id, name)
friends (from, to)
And I have the following query. It is supposed to retrieve all users with their friends :
SELECT user.id, user.name, f.to, friend.id, friend.name
FROM user
LEFT JOIN friends f ON user.id = f.from
LEFT JOIN user friend ON user.id = f.to
LIMIT 0, 200
It returns something like this:
id name from to id name
1 bob 1 3 NULL NULL
1 bob 1 4 NULL NULL
2 toto 2 7 NULL NULL
The from and two are correct, but the second join doesn't seem to work. Do you have any ideas what is wrong with the second join ?
Try this:
SELECT user.id, user.name, f.to, friend.id, friend.name
FROM user
LEFT JOIN friends f ON user.id = f.from
LEFT JOIN user friend ON friend.id = f.to
LIMIT 0, 200
Note that I replaced user with friend in the join condition.