Cakephp reciprocal friendship system - mysql

I've been trying to figure out for a week or so, how I can make a proper friendship system in CakePHP. I've read this and this thread but I can't get it to work.
I've read a lot more threads regarding this, but nobody seems to have a proper example.
I currently have a table users (id, username, password, e-mail etc.) and a table friendships (id, user_to, user_from, status).
Step 1 - Friendship request
If a user does a friendship request, then a row is inserted with the requesting user_id and the user_id of the user from whom the friendship is request, so it could look like:
id | user_from | user_to| status
1 | 1 | 2 | 0
This way I can easily show pending friends of user_id = 2, by selecting all records where user_to = 2
Step 2 - Confirm friendship
I've set it up so that user_id 2 now sees that user_id 1 wants to become friends, if he clicks the confirmation link, the status will be changed to 1, see below
id | user_from | user_to| status
1 | 1 | 2 | 1
I created all kinds of checks so the row stays unique.
Step 3 - Show friends
I thought this would be easy, if I want to show the friends of user_id = 1 then I just do a select with user_from = 1 OR user_to = 1, however this doesn't work.
User_id 1 can be a requester but can also be requested, so a JOIN will show strange results.
Does anyone know a solution? I'm happy to rebuild the entire system if I'm not doing the entire thing right! Any hints in the right direction are welcome as well...

Here is my solution : the difficulty lies in the correct request because friend requests can be crossed (if A asks B or B asks A will be stored the opposite way in the "to" and "from" fields of the table). Lets do it like that and user UNION and aliases to get friends from any user independently of the relation table bellow.
The [friends] table (relation): to|from|statut(pending,confirmed)
"to" and "from" > foreign_keys constraint to a [users] table
The request below always gives the wanted results ! (replace %d by the user ID or user Id in the SESSION
SELECT
users.userNickname,
friends.to AS friendUser,
friends.from AS currentUser,
friends.statut
FROM
users
INNER JOIN
friends
ON
users.userId = friends.to
WHERE
friends.from = '%d'
UNION
SELECT
users.userNickname,
friends.from AS friendUser,
friends.to AS currentUser,
friends.statut
FROM
users
INNER JOIN
friends
ON
users.userId = friends.from
WHERE
friends.to = '%d'

You can find friend requests to ID = 1 this way:
select * from Users u1 where u1.user_to = 1 and u1.user_from not in (select u2.user_to
from Users u2 where u2.user_from = u1.user_to)
You can find friend requests from ID = 1 this way:
select * from Users u1 where u1.user_from = 1 and u1.user_to not in (select u2.user_from
from Users u2 where u2.user_to = u1.user_from)
You can find mutual friendships of ID = 1 this way:
select * from Users u1 where ((u1.from = 1) or (u1.to = 1)) and 0 < (select count(*) from
Users u2 where u1.from = u2.to and u1.to = u2.from)
This code was not tested, but you get the idea.

Related

Mysql query to select if a user's friends list is the logged in user's friend

I am trying to get the friend list of a user profile. That's easy because of the schema I did.
Here is the table I did and demo data http://sqlfiddle.com/#!9/f78cf7/2
If I want to get the friend of user id 1, I would run select * from friends where user_id = 1 and I will get friends of this user. This is will return only first row with friend_id = 21.
Now how can I know if user id 30 is also a friend with id 21 in same query? How can I get the status column in same query?
Thank you.
You can try the below way -
DEMO
select f.user_id,f.friend_id,f.status
from friends f join friends f1 on f.id=f1.id+1
where f.status='Friend' and f1.status='Friend'
You can search both sides. For example:
select *
from friends where user_id = 30 and friend_id = 21 and status = 'Friend'
or user_id = 21 and friend_id = 30 and status = 'Friend'

same table select friend request

I am writing a chat database.
If users are friends, then they have each other in the friends table, like user 1 and 2 or 1 and 5 in my table(see link).
But I want to find out who sent the application as a friend. In this case, only one person stores the id of another person, like user 1 who sent request for user 7.
I have table:
(only 3 column: idfriend, iduser, friend_id)
How i can select user id (friend_id) who sent request for friend?
If i chose user 1, then my sql must return this friend request:
|friend_id|
7
i try this:
select friend.friend_id from friend inner join friend as friends on
friend.iduser != friends.friend_id and friends.iduser != friend.friend_id
and friend.iduser != friends.iduser and friend.friend_id !=
friends.friend_id where friend.iduser = 1
but this select 7,2 and 5. Need only 7
With NOT EXISTS:
select t.friend_id from friend t
where t.iduser = 1
and not exists (
select 0 from friend
where iduser = t.friend_id and friend_id = t.iduser
)
See the demo.
Results:
| friend_id |
| --------- |
| 7 |
Hopefully this is the solution you are looking for:
select t1.iduser, t1.friend_id
from friends t1
where t1.friend_id not in
(
select t2.iduser
from friends t2
where t2.friend_id = t1.iduser
)

Friends my Friends recently add

Im building a platform where users can connect with other users (Social platform)
I have a Table called friends and i am saving the connections like this
user_id | friend_id | request | add_date
Now i need to write a sql query to get the most recent friends a specific user's friends added that they are or not already friends of the user. Also the user must be accepted.
Think of it as a news feed and i was to see who my friends recently added (the new recently add person can be my friend or not)
So far i have this but works only when my friend added people i already have.
SELECT user_main_id AS frmname, friend_id AS type_id, add_date AS date
FROM friends
WHERE friend_id
IN
(SELECT friend_id
FROM friends WHERE (friend_id='$user_id' OR user_main_id='$user_id')
AND request=1 AND friend_id!=$user_id)
AND request=1 AND friend_id!=$user_id AND user_main_id!=$user_id
ORDER BY date DESC
Maybe there is a better why to approach this.
Suggestions? Much appreciated thanks. The connection is bilateral, no difference between user_id and friend_id. Was designed with those names and had to be carried forward.
Sample Record
96618 50683 1 2013-05-08 13:44:31
96618 1230 1 2013-04-03 18:28:51
11671 96618 1 2013-04-03 13:26:51
11671 1230 1 2013-03-23 18:26:08
Once 96618 connects with 50683 happens. users 11671 for example will get a msg saying your friend 96618 is now friends with 50683
Try this:
SELECT f2.friend_id
FROM friends f1 # Friends of target user
INNER JOIN friends f2
ON f1.friend_id = f2.user_id # Friends of their friends
AND f2.request = 1
AND f2.friend_id != f1.user_id
INNER JOIN friends f3 # Limiting it to mutual friends
ON f2.friend_id = f3.user_id AND f3.friend_id = f1.user_id
WHERE f1.user_id = 11671 AND f1.request = 1
ORDER BY f2.add_date DESC
Please note that with your current table structure, you are going to need two rows for each friendship, one for each direction. To do it with only one row, you probably want to split it into two tables - friendships and friendship_members.

MySQL Query to find friends and number of mutual friends

I have looked through the questions but I cant find anything that does exactly what I need and I can't figure out how to do it myself.
I have 2 tables, a user table and a friend link table. The user table is a table of all my users:
+---------+------------+---------+---------------+
| user_id | first_name | surname | email |
+---------+------------+---------+---------------+
1 joe bloggs joe#test.com
2 bill bloggs bill#test.com
3 john bloggs john#test.com
4 karl bloggs karl#test.com
My friend links table then shows all relationships between the users, for example:
+--------=+---------+-----------+--------+
| link_id | user_id | friend_id | status |
+---------+---------+-----------+--------+
1 1 3 a
2 3 1 a
3 4 3 a
4 3 4 a
5 2 3 a
6 3 2 a
As a note the a in the status column means approved, there could also be r(request) and d(declined).
What I want to do is have a query where if a user does a search it will bring back a list of users that they are currently not already friends with and how many mutual friends each user has with them.
I have managed to get a query for all users that are currently not friends with them. So if the user doing the search had the user id of 1:
SELECT u.user_id,u.first_name,u.surname
FROM users u
LEFT JOIN friend_links fl
ON u.user_id = fl.user_id AND 1 IN (fl.friend_id)
WHERE fl.friend_id IS NULL
AND u.user_id != 1
AND surname LIKE 'bloggs'
How then do I have a count of the number of mutual friends for each returned user?
EDIT:
Just as an edit as I don't think I am being particularly clear with my question.
The query that I currently have above will produce the following set of results:
+---------+------------+---------+
| user_id | first_name | surname |
+---------+------------+---------+
2 bill bloggs
4 karl bloggs
Those are the users matching the surname bloggs that are not currently friends with joe bloggs (user id 1).
Then I want to have how many mutual friends each of these users has with the user doing the search so the returned results would look like:
+---------+------------+---------+--------+
| user_id | first_name | surname | mutual |
+---------+------------+---------+--------+
2 bill bloggs 1
4 karl bloggs 1
Each of these returned users has 1 mutual friend as joe bloggs (user id 1) is friends with john bloggs and john bloggs is friends with both returned users.
I hope this is a bit more clear.
Thanks.
Mutual friends can be found by joining the friend_links table to itself on the friend_id field like so:
SELECT *
FROM friend_links f1 INNER JOIN friend_links f2
ON f1.friend_id = f2.friend_id
WHERE f1.user_id = $person1
AND f2.user_id = $person2
But bear in mind that this, in its worst case, is essentially squaring the number of rows in the friend_links table and can pretty easily jack up your server once you have a non-trivial number of rows. A better option would be to use 2 sub-queries for each user and then join the results of those.
SELECT *
FROM (
SELECT *
FROM friend_links
WHERE user_id = $person1
) p1 INNER JOIN (
SELECT *
FROM friend_links
WHERE user_id = $person1
) p2
ON p1.friend_id = p2.friend_id
Also, you can simplify your friend_links table by removing the surrogate key link_id and just making (user_id,friend_id) the primary key since they must be unique anyway.
Edit:
How would this be applied to the original query of searching for users that aren't already friends, I would like to do both in a single query if possible?
SELECT f2.user_id, COUNT(*) 'friends_in_common'
FROM friend_links f1 LEFT JOIN friend_links f2
ON f1.friend_id = f2.friend_id
WHERE f1.user_id = $person
GROUP BY f2.user_id
ORDER BY friends_in_common DESC
LIMIT $number
I am also thinking that the user_id constraints can be moved from the WHERE clause into the JOIN conditions to reduce the size of the data set created by the self-join and preclude the use of subqueries like in my second example.
This query lists anyone who's not friend with user 1 and whose surname matches '%bloggs%':
SELECT
users.user_id,
users.first_name,
users.surname,
Sum(IF(users.user_id = friend_links_1.friend_id, 1, 0)) As mutual
FROM
users inner join
(friend_links INNER JOIN friend_links friend_links_1
ON friend_links.friend_id = friend_links_1.user_id)
ON friend_links.user_id=1 AND users.user_id<>1
WHERE
users.surname LIKE '%bloggs%'
GROUP BY
users.user_id, users.first_name, users.surname
HAVING
Sum(IF(users.user_id = friend_links.friend_id, 1, 0))=0
just change the user id on the ON clause, and the surname on the WHERE clause. I think it should work correctly now!
If A is friend of B, then B is also a friend of A? Wouldn't it be better to use just a link instead of two links (and instead of two rows in friends_links)? Then you have to use two status columns, status1 and status2, and A is friend of B only if status1 = status2 = "a".
There are many ways to show mutual friends, e.g.:
SELECT friend_id
FROM friend_links
WHERE friend_links.user_id = $user1 or friend_links.user_id = $user2
AND NOT (friend_links.friend_id = $user1 or friend_links.friend_id = $user2)
GROUP BY friend_id
HAVING Count(*)>1
And this query shows for each user and anyone who's not his/her friend:
SELECT
users.user_id,
users.first_name,
users_1.user_id,
users_1.first_name
FROM
users INNER JOIN users users_1 ON users.user_id <> users_1.user_id
WHERE
NOT EXISTS (SELECT *
FROM friend_links
WHERE
friend_links.user_id = users.user_id
AND friend_links.friend_id = users_1.user_id)
(The only think I didn't check is the friendship status, but it's easy to add that check).
I'm still working on it, but it's not easy to combine nicely these two queries togheter. So this isn't exactly an answer, I'm just showing some ideas that i've tried.
But what do you need exactly? A query that returns every user with anyone who's not his/her friend and the number of friends in common, or is the user_id already given?
With some code it's not a problem to answer your question... but there has to be a nice way just by using SQL! :)
EDIT:
I'm still wondering if there's a better solution to this, in particular the next query could be extremely slow, but it looks like this might work:
SELECT
users_1.user_id,
users_2.user_id,
Sum(IF(users_1.user_id = friend_links.user_id AND users_2.user_id = friend_links_1.friend_id, 1, 0)) As CommonFriend
FROM
users users_1 INNER JOIN users users_2
ON users_1.user_id <> users_2.user_id,
(friend_links INNER JOIN friend_links friend_links_1
ON friend_links.friend_id = friend_links_1.user_id)
GROUP BY
users_1.user_id,
users_2.user_id
HAVING
Sum(IF(users_1.user_id = friend_links.user_id AND users_2.user_id = friend_links.friend_id, 1, 0))=0
(as before, i didn't check friendship status)
If user is given, you could put WHERE users_1.user_id=$user1 but it's better to just leave one user table, and filter the next INNER JOIN whith that user.

How to model Friendship relationships

I have been trying to figure out how to do this, and even with looking at other examples, I can't get it figured out, so maybe I can get some personalized help.
I've got two tables, users_status and friendships.
In the users_status table I have a field userid, and several others.
In the friendships table, I have the fields request_to,request_from, and friendship_status.
Basically what I want to do is get all of the status posts by the current user AND those who are friends of the current user (which I can specify in my PHP using a $userid variable).
Here's an example of the friendships table structure. When a friend request is sent, the userid of the sender and receiver are placed in the table, with a friendship_status of 0. When the request is accepted, the friendship_status is set to 1 and those two are now friends.
friendship_id request_from request_to friendship_status
1 111248 111249 1
2 111209 111249 1
3 111209 111248 0
11 111209 111259 1
5 111252 111209 1
12 111261 111209 1
I realize this may not even be the best structure for determining friendships, especially since the site is relationship based and having to check for friendship connections will be a frequently used thing.
Would it perhaps be better to have two separate tables for friend_requests and friendships? If so, how would I structure/manage the friendships table?
You can use a table join (e.g. http://dev.mysql.com/doc/refman/5.0/en/join.html) to find all of the requests.
Actually you can use a subquery here:
SELECT * FROM users_status WHERE userid = "$userid"
OR userid in (SELECT request_to FROM friendships where request_from = "$userid" AND friendship_status = 1)
OR userid in (SELECT request_from FROM friendships where request_to = "$userid" AND friendship_status = 1)
replace $userid with your user id
The simplest schema I can think of is:
PENDING_FRIENDSHIPS(request_from, request_to)
FRIENDSHIPS(request_from, request_to)
I also removed the ID because both fields on both tables will be compound primary keys (request_from, request_to).
To get all friends from the current user just run:
select * from friendships
where $currentUser = request_from OR $currentUser = request_to
This would return both columns and you would have to remove in PHP the current user.
Another way to get all friends from this schema is to run a UNION:
select request_from from friendships
where request_to = $currentUser
UNION
select request_to from friendships
where request_from = $currentUser
The drawback of this solution is that you're running 2 selects