Friends of Friends query for 1 way relationships - mysql

This is my table: (friends)
user_id
friend_id
1
2
1
3
1
4
2
1
2
5
3
7
3
8
4
1
4
5
9
2
10
1
11
8
11
7
The table contains 1 way relationship. For eg. User 1 is following User 3, but User 3 is not following User 1.
So Each user has Followers and Following data.
What I want in my output is:
Get Followers of selected user + followers and following of those Followers
Plus (+)
Get Followings of selected user + followers and following of those Followings
Desired Output:-
user_id
friend_id
1
2
1
3
1
4
2
1
2
5
3
7
3
8
4
1
4
5
9
2
10
1
This is my current query:
SELECT t1.*
FROM friends t1
JOIN friends t2 ON t1.friend_id=t2.user_id
WHERE t2.friend_id=$user_id
UNION
SELECT t1.*
FROM friends t1
JOIN friends t2 ON t1.friend_id=t2.friend_id
WHERE t2.friend_id=$user_id
;
I got this query from here: How to get friends of friends of friends... (#Adams answer)
But I don't think it is looping through all the condition that I want in my case. Any help would be much appreciated, thanks.
Edit:- #sajjad rezaei answer satisfies all the conditions, but is not exactly the output that I wanted.
(my bad, I should have added my desired output.)
Here is the dbfiddle link of the output:- https://www.db-fiddle.com/f/sWVYZKVWMbbEUkShJmje2Q/5

With this query:
SELECT CASE WHEN user_id = 1 THEN friend_id ELSE user_id END id
FROM friends
WHERE 1 IN (user_id, friend_id);
you get all the followers and followings of user = 1.
Join it to the table:
SELECT DISTINCT f.user_id, f.friend_id
FROM friends f
INNER JOIN (
SELECT CASE WHEN user_id = 1 THEN friend_id ELSE user_id END id
FROM friends
WHERE 1 IN (user_id, friend_id)
) t ON t.id IN (f.user_id, f.friend_id)
ORDER BY f.user_id, f.friend_id;
See the demo.

The fiddle
Here's one for MySQL 5.7 (8.0 or less):
SELECT user_id, friend_id
FROM (
SELECT f.user_id, f.friend_id FROM friends AS f WHERE 1 IN (f.user_id, f.friend_id) UNION
SELECT f.user_id, f.friend_id
FROM (
SELECT f.user_id, f.friend_id FROM friends AS f WHERE 1 IN (f.user_id, f.friend_id)
) AS f0
JOIN friends AS f
ON f0.user_id IN (f.friend_id, f.user_id)
OR f0.friend_id IN (f.friend_id, f.user_id)
) AS fof
ORDER BY user_id, friend_id
;
Here's one for MySQL 8.0+:
WITH RECURSIVE fof (user_id, friend_id, n) AS (
SELECT f.user_id, f.friend_id, 1 FROM friends AS f WHERE 1 IN (f.user_id, f.friend_id) UNION ALL
SELECT f.user_id, f.friend_id, n+1
FROM friends AS f
JOIN fof AS f2
ON ( f.user_id IN (f2.friend_id, f2.user_id)
OR f.friend_id IN (f2.friend_id, f2.user_id)
)
AND n < 2
)
SELECT DISTINCT user_id, friend_id
FROM fof
ORDER BY user_id, friend_id
;
The result:
user_id
friend_id
1
2
1
3
1
4
2
1
2
5
3
7
3
8
4
1
4
5
9
2
10
1
I used the following to count the larger data set...
Non-recursive:
SELECT COUNT(*)
FROM (
SELECT f.user_id, f.friend_id FROM friends AS f WHERE 1066 IN (f.user_id, f.friend_id) UNION
SELECT f.user_id, f.friend_id
FROM (
SELECT f.user_id, f.friend_id FROM friends AS f WHERE 1066 IN (f.user_id, f.friend_id)
) AS f0
JOIN friends AS f
ON f0.user_id IN (f.friend_id, f.user_id)
OR f0.friend_id IN (f.friend_id, f.user_id)
) AS fof
;
Recursive:
WITH RECURSIVE fof (user_id, friend_id, n) AS (
SELECT f.user_id, f.friend_id, 1 FROM friends AS f WHERE 1066 IN (f.user_id, f.friend_id) UNION ALL
SELECT f.user_id, f.friend_id, n+1
FROM friends AS f
JOIN fof AS f2
ON ( f.user_id IN (f2.friend_id, f2.user_id)
OR f.friend_id IN (f2.friend_id, f2.user_id)
)
AND n < 2
)
, xxx AS (
SELECT DISTINCT user_id, friend_id
FROM fof
ORDER BY user_id, friend_id
)
SELECT COUNT(*) FROM xxx
;
The result:
+----------+
| COUNT(*) |
+----------+
| 12125 |
+----------+

It might help you with your situation:
(SELECT f1.* ,
(SELECT GROUP_CONCAT(friend_id) from friends WHERE user_id = f1.friend_id) AS followerOFfollowers,
(SELECT GROUP_CONCAT(user_id) from friends WHERE friend_id = f1.friend_id) AS followingOFfollowers
FROM friends as f1
WHERE f1.user_id = 1 GROUP by f1.id , f1.friend_id)
UNION
(SELECT f1.* ,
(SELECT GROUP_CONCAT(friend_id) from friends WHERE user_id = f1.user_id) AS followerOFfollowers,
(SELECT GROUP_CONCAT(user_id) from friends WHERE friend_id = f1.user_id) AS followingOFfollowers
FROM friends as f1
WHERE f1.friend_id = 1 GROUP by f1.id , f1.user_id)

Related

Setting a clause for cross join (sql)

I am looking to show a mutual friends function but my user friends list is stored on another table.
I want to know if it's possible to use the data of each row as a clause for join B
SELECT * FROM ( SELECT * FROM `users`) a
CROSS JOIN (SELECT COUNT(*) AS MUTUAL_FRIENDS
FROM `user_friends` WHERE `friend_to` = `a`.`userid`) b
ORDER BY `b`.`MUTUAL_FRIENDS` ASC
What I am looking to do is use the userid from users table as a clause for the join with:
`friend_to` = `a`.`userid`
Expected results:
+--------=+---------+-----------+--------------+
| userid | Username |Photo_URL |MUTUAL_FRIENDS|
+---------+---------+-----------+--------------+
1 Somename1 /image1.png 3
4 Somename4 /image4.png 2
2 Somename2 /image2.png 1
3 Somename3 /image3.png 0
5 Somename5 /image5.png 0
Friends table:
+--------=+---------+-----------+
| friend_id | userid |friend_to |
+---------+---------+-----------+
1 1 2
2 2 1
3 1 3
4 3 1
5 1 4
6 4 1
Users Table
+--------=+---------+-----------+
| userid | Username |Photo_URL |
+---------+---------+-----------+
1 Somename1 /image1.png
2 Somename2 /image2.png
3 Somename3 /image3.png
4 Somename4 /image4.png
5 Somename5 /image5.png
Or am I doing this totaly wrong?
Here is one approach.
With CROSS JOIN
SELECT u.userid,u.Username,u.Photo_URL,IFNULL(Num,0) MUTUAL_FRIENDS
FROM Users u
LEFT JOIN (SELECT userid, COUNT(*) Num
FROM (SELECT a.userid
FROM Friends a
CROSS JOIN Friends b
WHERE a.userid = b.friend_to and a.friend_to = b.userid) r
GROUP BY userid) k ON u.userid = k.userid
ORDER BY MUTUAL_FRIENDS DESC, u.userid
Without CROSS JOIN
SELECT u.userid,u.Username,u.Photo_URL,IFNULL(Num,0) MUTUAL_FRIENDS
FROM Users u
LEFT JOIN (SELECT userid, COUNT(*) Num
FROM (SELECT a.userid
FROM Friends a, Friends b
WHERE a.friend_to = b.userid and b.friend_to = a.userid) r
GROUP BY userid) k ON u.userid = k.userid
ORDER BY MUTUAL_FRIENDS DESC, u.userid
I will try to explain little by little and please correct me if there is error.
The condition a.friend_to = b.userid and b.friend_to = a.userid is
used to select only mutual friend. Then if a user has this kind of
"mutual connection," the corresponding userid will appear once.
Get the count along with the userid.
Left join the userid and count table.
Result
userid Username Photo_URL MUTUAL_FRIENDS
1 Somename1 /image1.png 3
2 Somename2 /image2.png 1
3 Somename3 /image3.png 1
4 Somename4 /image4.png 1
5 Somename5 /image5.png 0
Given the data, user1 is mutual friend of user2, user3 and user4. User2 is only mutual friend of user 1 and so as user 3 and user4.
With CROSS JOIN SQL Fiddle
Without CROSS JOIN SQL Fiddle

how to use select query in a Group_concat sub query in mysql

I have 3 tables:
tbl_user stores all user details (user_id,name,address)
user_id name address
1 a (shop) home
2 b (shop) bakerstreet
3 c (staff) wallstreet
4 d (staff) georgia
5 e (staff) sydney
tbl_user_group stores user type (user_id,user_type : 1=shop_owner,2=staff)
user_id user_type
1 1
2 1
3 2
4 2
5 2
tbl_user_association holds the shop_owner and staff relation (shop_owner_id, staff_id)
shop_owner_id staff_id
1 3
1 4
1 5
2 3
2 4
desired result
i want to display the list of staffs and the respective shops that they are associated with as follows:
user_id staff_name shop_owner
3 c a,b
4 d a,b
5 e a
I tried using the group_concat as mentioned here. The query is as follows:
SELECT
u.id AS user_id,
u.name AS staff_name,
(SELECT GROUP_CONCAT(u.name separator ',') FROM tbl_user u WHERE u.id = ua.shop_owner_id) AS
shop_owner
FROM tbl_user u
JOIN tbl_user_group ug ON u.id = ug.user_id
LEFT JOIN tbl_user_association ua ON u.id = ua.staff_id
WHERE ug.user_type = 2
GROUP BY u.id
But it returns single row of staffs as below. where have i gone wrong?
user_id staff_name shop_owner
3 c a
4 d a
5 e a
This is how I'd do it:
SELECT
u.user_id,
u.name,
GROUP_CONCAT( so.name )
FROM
tbl_user_group ug
INNER JOIN tbl_user u
ON ( ug.user_id = u.user_id )
INNER JOIN tbl_user_association ua
ON ( ua.staff_id = u.user_id )
INNER JOIN tbl_user so -- here join again the user table to get shop owners names
ON ( ua.shop_owner_id = so.user_id )
WHERE
ug.user_type = 2
GROUP BY
u.user_id;

Inner Join with two columns

I want to make a selection that look up values of ID to Username from two columns in the same table:
Table: Gift
FromID ToID
1 2
2 6
3 2
5 3
TableL Users
UserID Username
1 A
2 B
3 C
4 D
5 E
6 F
Expected Output:
FromUser ToUser
A B
B F
C B
E C
Here is one trick using JOIN with IN condition
SELECT Max(CASE WHEN FromID = u.UserID THEN Username END),
Max(CASE WHEN ToID = u.UserID THEN Username END)
FROM users u
JOIN gift g
ON u.UserID IN ( g.FromID, g.ToID )
GROUP BY FromID,
ToID
Or you need to join the users table twice
SELECT f.Username,
t.Username
FROM gift g
LEFT JOIN users f
ON f.UserID = g.FromID
LEFT JOIN users t
ON t.UserID = g.ToID
Looks like you need a recap on how to work with several relationships between the same two tables.
Your gift table has a relationship that describes who gave the gift - I call it giver here; and a relationship that described who received the gift - I call it receiver here. Both relationships from gift are many-to-one towards the users table.
You simply join the same table twice and give that table a good correlation name - giver and receiver in my example - to show in the code what you're using them for.
Like so:
WITH
gift(fromid,toid) AS (
SELECT 1,2
UNION ALL SELECT 2,6
UNION ALL SELECT 3,2
UNION ALL SELECT 5,3
)
,
users(userid,username) AS (
SELECT 1,'A'
UNION ALL SELECT 2,'B'
UNION ALL SELECT 3,'C'
UNION ALL SELECT 4,'D'
UNION ALL SELECT 5,'E'
UNION ALL SELECT 6,'F'
)
SELECT
giver.username as fromUser
, receiver.username as toUser
FROM gift
JOIN users AS giver
ON gift.fromid=giver.userid
JOIN users AS receiver
ON gift.toid =receiver.userid
ORDER BY 1,2
;
fromUser|toUser
A |B
B |F
C |B
E |C

Followers of a user and ids of last 2 comments liked by each follower(if any), also the count of follower's followers, using mysql

I have social application let's say like twitter, where user can follow other users and can likes some comments.
What I need to fetch is followers of a user and ids of last 2 comments liked by each follower(if any), also the count of follower's followers, using MySQL.
Here are the tables
Table user_follower
User_id follower_id
1 2
2 3
1 5
1 6
1 7
Table user_likes
comment_id User_id date
41 2 some_date
42 2 some_date
41 5 some_date
42 5 some_date
43 5 some_date
43 2 some_date
43 6 some_date
how can we do this in a single mysql query?
so far i am able to get the followers and count of follower's follower and following both.
select uf.follower_id,
(select count(*) from user_followers uf1 where uf1.follower_id = uf.follower_id) as following_count,
(select count(*) from user_followers uf2 where uf2.user_id = uf.follower_id) as follower_count,
from user_followers uf
join users u on u.id = uf.follower_id
where uf.user_id = 1
what i want is now to get the 2 latest comment_ids for each follower, i.e. uf.follower_id here.
if not possible in the same query,
i am fine even with another query as will passing the follower_ids as in parameter, but it should give me 2 latest comment for each passed id..
I think this will work. I have not tested it so it may have some syntax errors. Given the level of nesting used in this query I suspect it will perform badly with a very large dataset.
SELECT follower_id, num_followers, GROUP_CONCAT(comment_id)
FROM (
SELECT t.*,
#r := IF(#g = t.follower_id, #r+1, 1) RowNum,
#g := t.follower_id
FROM (select #g:=null) AS initvars
INNER JOIN (
SELECT followers.*, ul.comment_id
FROM (
SELECT
uf1.user_id,
uf1.follower_id,
COUNT(uf2.follower_id) AS num_followers
FROM user_follower uf1
LEFT JOIN user_follower uf2
ON uf1.follower_id = uf2.user_id
WHERE uf1.user_id = 1
GROUP BY uf1.user_id, uf1.follower_id
) AS followers
LEFT JOIN user_likes ul
ON followers.follower_id = ul.user_id
ORDER BY followers.follower_id ASC, comment_id DESC
) AS t
) AS final
WHERE RowNum < 3
GROUP BY follower_id, num_followers;
UPDATE Here is the other query using the inequality join -
SELECT tmp.follower_id, COUNT(uf2.follower_id) AS num_followers, tmp.comments
FROM (
SELECT follower_id, GROUP_CONCAT(comment_id ORDER BY comment_id DESC) AS comments
FROM (
SELECT uf.follower_id, ul1.*
FROM user_follower uf
LEFT JOIN user_likes ul1
ON uf.follower_id = ul1.user_id
LEFT JOIN user_likes ul2
ON uf.follower_id = ul2.user_id
AND ul1.comment_id < ul2.comment_id
WHERE uf.user_id = 1
GROUP BY ul1.user_id, ul1.comment_id
HAVING COUNT(ul2.comment_id) < 2
) AS tmp
GROUP BY tmp.follower_id
) AS tmp
LEFT JOIN user_follower uf2
ON tmp.follower_id = uf2.user_id
GROUP BY tmp.follower_id

MySql Get voters who voted the same

I have 4 tables:
Users
record_id first_name last_name
1 John Smith
2 Jim Brown
3 Jane Goodall
Polls
record_id poll_question
1 What is your age?
2 What is your occupation?
Poll Options
record_id poll_id option_text
1 1 16-20
2 1 21-25
3 2 builder
4 2 technician
Poll Votes
record_id user_id poll_id option_id
1 1 1 1
2 1 2 1
3 2 1 2
4 2 2 1
Given a specified user, how do I get all OTHER users who selected the same options for the polls answered by the specified user.
Ideally, it would provide a descending list of users according to how many questions were answered the same, i.e. users who voted all exactly the same would be at the top, down to users with no answers in common.
SELECT u.first_name, u.last_name, v.Answers
FROM Users AS u
LEFT JOIN (
SELECT pv.user_id AS user, COUNT(*) AS Answers
FROM PollVotes AS pv
WHERE ((poll_id, option_id) IN
(
SELECT poll_id, option_id
FROM PollVotes
WHERE user_id = YOURUSER
))
AND pv.user_id != YOURUSER
GROUP BY pv.user_id
) AS v
ON u.record_id = v.user
WHERE u.record_id != YOURUSER
ORDER BY v.Answers DESC
The inner query selects all users with the same (poll_id, option_id) combination as the selected user. The rowcount per user is the number of common answers. The left join with the user table is to include users with no common answers in the result.
Here's another approach:
SELECT u1.record_id, u1.first_name, u1.last_name, u2.record_id comp_record_id, u2.first_name comp_first_name, u2.last_name comp_last_name, u1.options FROM (
SELECT u.record_id, u.first_name, u.last_name, GROUP_CONCAT(pv.poll_id,'.', pv.option_id ORDER BY pv.poll_id, pv.option_id) options
FROM users u
INNER JOIN poll_votes pv ON pv.user_id = u.record_id
GROUP BY u.record_id
) u1
INNER JOIN (
SELECT u.record_id, u.first_name, u.last_name, GROUP_CONCAT(pv.poll_id,'.', pv.option_id ORDER BY pv.poll_id, pv.option_id) options
FROM users u
INNER JOIN poll_votes pv ON pv.user_id = u.record_id
GROUP BY u.record_id
) u2 ON u1.options = u2.options AND u1.record_id <> u2.record_id
WHERE u1.record_id = 1;
The two inner queries are identical, and could actually be turned into a view. The outer query simply joins the two on the question / answer lists to get the matches.