Efficient friend suggestion sql query without using php - php

I have a members table in which every site member is the unique id.
eg
id firstname secondname emailaddress country city gender
the second table is a friends table with the following structure
id meid friendid date
what query would i use to get friend suggestions of a particular user based on mutual friends and sorted accordingly. before i was using php to loop thru and collect mutual friends but as the site grew, php started misbehaving and running out of memory.
This is the function i was using
//-----------------------------------------------
function getFriendSuggestions($id)
{
$friendids=getFriendIdArray($id); //returns list of your friends
$networkids=getNetworkIdArray($id);//returns list of all members in your network(friends and their friends)
$diff=array_merge(array(),array_diff($networkids,$friendids));
$diff_mutual=array();
$diff_mutual_total=array();
for ($n=0;$n<count($diff);$n++)
{
$ff=getFriendIdArray($diff[$n]);
$mf=array_merge(array(),array_intersect($ff,$friendids));
$diff_mutual[]=$mf;
$diff_mutual_total[]=count($mf);
}
$diff=array_merge(array(),$diff);
$diff_mutual=array_merge(array(),$diff_mutual);
$diff_mutual_total=array_merge(array(),$diff_mutual_total);
$w=$diff_mutual_total;
arsort($w);
$d=array();
$dm=array();
foreach ($w as $key => $value)
{
$d[]=$diff[$key];
$dm[]=$diff_mutual[$key];
}
$cv=array($d,$dm);
return $cv;
}

We say that B is possibly-friend of A if there is a large number of entries of the kind
(B, someguy) (someguy, A)
in the database, and there is no (B, A) entry.
We know the ID of user A and let it be AID. Then we can do:
SELECT b.meid, COUNT(b.meid) AS incommon
FROM friends AS b
JOIN friends AS a ON (b.friendid = a.meid AND a.friendid = AID)
GROUP BY b.meid ORDER BY incommon DESC;
This will tell us all "possible friends" of A, including those that are
already friends of A. Then we have to exclude them:
SELECT maybe.meid, maybe.incommon FROM
( SELECT b.meid, COUNT(b.meid) AS incommon
FROM friends AS b
JOIN friends AS a ON (b.friendid = a.meid AND a.friendid = AID)
GROUP BY b.meid ORDER BY incommon DESC ) AS maybe
LEFT JOIN friends AS already ON (maybe.friendid = already.meid AND already.friendid = AID) WHERE already.friendid IS NULL;
Then we need to populate the rest of the fields:
SELECT members.firstname, members.secondname, maybe.incommon FROM
( SELECT b.meid, COUNT(b.meid) AS incommon
FROM friends AS b
JOIN friends AS a ON (b.friendid = a.meid AND a.friendid = AID)
GROUP BY b.meid ORDER BY incommon DESC ) AS maybe
LEFT JOIN friends AS already ON (maybe.friendid = already.meid AND already.friendid = AID)
JOIN members ON (members.id = maybe.meid)
WHERE already.friendid IS NULL;
This will return friend suggestions for AID, including how many people in common it has for every choice (e.g. "John Doe (15 friends in common)", etc.).

You want "friends of my friends, but who are not also my friends".
SELECT
me.id AS member_id,
their_friends.friendid AS suggested_friend_id,
COUNT(*) AS friends_in_common
FROM
members AS me
INNER JOIN
friends_map AS my_friends
ON my_friends.meid = me.id
INNER JOIN
friends_map AS their_friends
ON their_friends.meid = my_friends.friendid
LEFT JOIN
friends_map AS friends_with_me
ON friends_with_me.meid = their_friends.friendid
AND friends_with_me.friendid = me.id
WHERE
friends_with_me.meid IS NULL
GROUP BY
me.id,
their_friends.friendid

Related

top first friends then the other users from like list

I have tables:
likes - id, user_id, like_user_id<br>
users - id, name, email ...,<br>
friends - id, user_id, friend_id, status<br>
Is it possible to sort it with one SQL query first to show the friends then the other users.
Any help would be appreciated.
Thank you.
I tried this and it works, but the problem is it give me double results of users:
select *
from `likes`
left join `users` on `users.id` = `likes.user_id`
left join `friends` on `friends.user_id` = `likes.user_id`
or `friends.friend_id` = `likes.user_id`
where `likes.id` = 1
order by `friends.user_id` = 5
or `friends.friend_id` = 5
You need to work with a UNION here to merge the liked users with the befriended users. Upon doing this, you can create an artificial column friend, that you fill with 1 in the friend query and 0 in the like query. Later on you can order by that column.
SELECT
friends.user_id,
1 as friend,
users.*
FROM
friends
JOIN users ON users.id = friends.friend_user_id
UNION SELECT
likes.user_id,
0 as friend,
users_liked.*
FROM
likes
JOIN users as users_liked ON likes.like_user_id = users_liked.id
WHERE
user_id = '$userId'
ORDER BY friend DESC, id ASC
This will return a list of all friends, followed by a list of all liked users.

MYSQL: Multiple "contradicting" conditions on one column

I'm trying to create a query that ONLY outputs members if they meet 2 conditions.
The first condition:
If the user has a matching "courseID".
The second condition:
If the user doesn't have a matching "courseID".
Obviously this is contradicting so let me explain.
I have two tables...
members
ID
email
certifications
ID
memberID
courseID
When members complete a course they gain a certification. The certification stores their memberID and the courseID. Members can have multiple certifications.
The end goal is to create an email sending list to promote courses.
Let's say we have two courses.
"Beginner"(courseID=1) and "Intermediate"(courseID=2).
One member could have both certifications, one for each course. Another member could just have the one, the "Beginner" certification.
I want to send a promotional email to all members who have the "Beginner" certification. However I want to exclude any members with the "Intermediate" certification.
The problem I can't seem to overcome is how to exclude the members with the "Intermediate" certification.
**I have simplified this example, in truth there is a vast amount of different certification types **
Below is a rough query I've been trying... I've tried so, so many different queries.
Any help would be great, thank you.
// Look Up Recipients
$recipientsQuery = $wpdb->get_results("
SELECT DISTINCT(email)
FROM $memberTable
LEFT JOIN $certificationsTable
ON $memberTable.ID = $certificationsTable.memberID
WHERE courseID = 4 AND courseID != 5
");
SELECT m.ID, m.email
, MAX(IF(c.courseID = 4, 1, 0)) AS hasCourse4
, MAX(IF(c.courseID = 5, 1, 0)) AS hasCourse5
FROM members AS m
LEFT JOIN certifications AS c ON m.ID = c.memberID
HAVING hasCourse4 = 1 AND hasCourse5 = 0
;
Alternatively, you could join certs to members to certs, this might be faster:
SELECT m.email
FROM certifications AS c1
INNER JOIN members AS m ON c1.memberID = m.ID
LEFT JOIN certifications AS c2 ON m.ID = c2.memberID AND c2.courseID = 5
WHERE c1.courseID = 4 AND c2.courseID IS NULL
;
Try this.. Get what you need, and take away what you dont..
SELECT *
FROM $memberTable
LEFT JOIN $certificationsTable USING (ID)
WHERE courseID = 4
AND ID NOT IN (
SELECT ID
FROM $memberTable
LEFT JOIN $certificationsTable USING (ID)
WHERE courseID = 5
)

Join 3 tables of users, friends, chats

I just want to get the result of a single user's friends and for each friend to join the last timestamp of the chat between the user and the friend:
User friends list:
Friend1: fname, lname,...., chat timestamp
Friend2: fname, lname,...., chat timestamp
...
users table is the main user information table:
usr_id,usr_fname,usr_lname
friends table is a table containing the friendships:
frd_id, frd_usr_id1, frd_usr_id2
chats table is a table containing chats between users:
cht_id, cht_usr_id1,cht_usr_id2,cht_timestamp
Can anyone help me with SQL statements?
Let say that the requesting user ID is in $user_id variable
Try this, it should work...
SELECT u1.usr_fname [Friend1First],
u1.usr_lname [Friend1Last],
u2.usr_fname [Friend2First],
u2.usr_lname [Friend2Last],
MAX(ch.cht_timestamp) [LastChatTime]
FROM Chats ch
JOIN users u1 ON ch.cht_usr_id1 = u1.usr_id
JOIN users u2 ON ch.cht_usr_id2 = u2.usr_id
WHERE EXISTS (SELECT frd_id from friends fs WHERE (fs.frd_usr_id1 = a.usr_id AND fs.frd_usr_id2 = b.usr_id)
OR (fs.frd_usr_id2 = a.usr_id AND fs.frd_usr_id1 = b.usr_id))
GROUP BY u1.usr_fname, u1.usr_lname, u2.usr_fname, u2.usr_lname,
This is the answer:
SELECT f.frd_id, u.usr_id, u.usr_fname, u.usr_lname, MAX(c.cht_timestamp)
FROM friends AS f
LEFT JOIN users AS u ON u.usr_id IN (
SELECT u1.usr_id FROM users AS u1 WHERE u1.usr_id != $user_id)
LEFT JOIN chats AS c ON ( (c.cht_usr_id1=$user_id AND c.cht_usr_id2=u.usr_id) OR
(c.cht_usr_id1=u.usr_id AND c.cht_usr_id2=$user_id) )
WHERE ( (f.frd_usr_id1=$user_id AND f.frd_usr_id2=u.usr_id) OR
(f.frd_usr_id1=u.usr_id AND f.frd_usr_id2=$user_id) )
GROUP BY u.usr_id ORDER BY MAX(c.cht_timestamp) DESC

Programming a friends list feature

I'd like to firstly point that I'm not very good at advanced MySQL just yet. So please forgive me. What I am trying to do is create a friends list like what's in the image below:
Here is my db structure:
friends table:
users table:
Here is my code so far:
<?php $query3 = $this->db->query("SELECT * FROM friends WHERE node1id = '{$myuserid}'");
foreach($query3->result() as $row1) {
echo $row1->node1id."<br>"; } ?>
I know this code isn't logically correct. What I am trying to do is pull in the users table. And if relationType = friends, display firstname and lastname of the user in the friends list. I have two variables. $selectedId and $myuserid. $selectedId is the id of the profile the user is viewing. and $myuserid is the id of the logged in user. How would I code this type of feature logically?
SELECT username FROM users
WHERE userid IN
(SELECT node2id FROM friends WHERE node1id = '{$myuserid}'
AND relationType = 'friends')
and
echo $row1->username
For mutual friends you could do this
EDIT: Just realised last version of this wasn't right, fixed now (untested)
SELECT username FROM users
WHERE userid IN
(SELECT node2id FROM (
(SELECT node2id FROM friends WHERE relationType = 'friends' AND node1id <> '{$myuserid}' AND node1id IN
(SELECT node2id FROM friends WHERE relationType = 'friends' AND node1id = '{$myuserid}')))
WHERE node2id IN (SELECT node2id FROM friends WHERE relationType = 'friends' AND node1id = '{$myuserid}'))
This first selects friends, then it selects the friends of those friends which is not equal (not equal is <>) to yourself. It then only selects from that list the friendIDs which match your your own friend IDs. It then selects the name of these users from the users table.
You can do a select inside of a select like below:
SELECT * FROM users WHERE idusers IN (
SELECT node2id FROM friends WHERE node1id = $myUserId
)
Here's how to get all friends of a particular user ($selectedid), and determine whether or not each of their friend is a mutual friend:
SELECT b.*,
c.myfrnd IS NOT NULL AS isMutual
FROM (
SELECT IF(node2id = '$selectedid', node1id, node2id) AS usrfrnd
FROM friends
WHERE '$selectedid' IN (node1id,node2id) AND
relationType = 'friends'
) a
JOIN users b ON a.usrfrnd = b.userid
LEFT JOIN (
SELECT IF(node2id = '$myuserid', node1id, node2id) AS myfrnd
FROM friends
WHERE '$myuserid' IN (node1id,node2id) AND
relationType = 'friends'
) c ON b.userid = c.myfrnd
ORDER BY isMutual DESC
The column isMutual will contain 1 if the friend is a mutual friend, otherwise 0 if not. Mutual friends show up first in the result set.
The thing you have to consider is that a user could be in either columns node1id or node2id, so in order to get a consistent join, we use a subselect to force the friends of the parameter userids ($selectedid and $myuserid) to be in the same column.
Let me know how this works.

joining two tables based on cookie data

I am making a cookie based favorite system and need to join data from two tables based on the unique user id stored in the cookie so I can tell what items that use has marked as favorites.
I know I need to do a JOIN but have not used them much and dont really have my head around them yet.
Existing query that selects the items from the db:
$query = mysql_query("SELECT *, UNIX_TIMESTAMP(`date`) AS `date` FROM songs WHERE date >= DATE_SUB( NOW( ) , INTERVAL 2 WEEK ) ORDER BY date DESC");
My favorites table is setup as: ID FAVORITE USERID where ID is the primary key, FAVORITE is the song ID from table songs and USERID is a hash stored in a cookie.
I need to join in all the rows from the favorites table where the USERID field matches the cookie hash variable.
I also need to gather the total number of rows in favorites that match the song id so I can display a count of the number of people who set the item as favorite so I can display how many people like it. But maybe need to do that as a separate query?
This should do it, I would imagine:
$user_id = intval($_COOKIE['user_id']);
$query = mysql_query(sprintf("
SELECT *
FROM songs s
INNER JOIN favorites f
ON f.favorite = s.id
WHERE f.userid = %s
", $user_id));
You should probably read up on the different types of joins.
And then to get the total amount of rows returned, you can just call mysql_num_rows on the result:
$favorite_song_count = mysql_num_rows($query);
EDIT: To select all songs but note which are favorited, you would do this:
$query = mysql_query(sprintf("
SELECT s.*, f.id as favorite_id
FROM songs s
LEFT JOIN favorites f
ON f.favorite = s.id AND f.userid = %s
", $user_id));
By switching it from an INNER JOIN to a LEFT JOIN we are selecting all songs even if they don't have a corresponding record in the favorites table. Any songs that are favorites of the user_id provided will have a non-NULL value for favorite_id.
You can have logical (and, or, ...) operators in join conditions:
select t1.*
from t1
join t2 on t1.id = t2.fid and t2.foo = 'blah'
If you are also querying the total number of times each song has been "favorited" then you need a group by construct also, like this way:
select *, count(f.id)
from songs as s
left join favorites as f on s.id = f.favorite and f.userid = <hash>
group by s.id

Categories