Search by Mutual Friends Count - Friend System Mysql PHP - php

I am creating a Friend System in my Forum.
I am having a tough time trying to figure out how I would grab users and order by the mutual_friend count.
I am trying to build a page that shows a list of recommended friends.
Here is my structure of tables:
users table
-----
user_id | name |
friends table
-----
friend_id | from_id | to_id
Here is an example of what is happening.
Suppose there are total of A,B,C,D,E,F = 6 people in the site.
I am A, and B,C are my friends.
D and E in turn are friends of B.
D is also a friend of C but E is not a friend of C.
F is not a friend of anyone in the site.
Therefore from above data it looks like D and E are mutual friends of me (A). F is not a mutual friend of mine.
Since D is a friend of both B and C and E is friend of only B:
A and D has 2 mutual friends.
A and E has 1 mutual friend.
A and F has 0 mutual friend.
Now if I want to search (remember i am A) for people who are not my friends I can do something like:
$myfriends = "2,3"; //putting all my friends in a variable
SELECT * FROM users WHERE user_id NOT IN( $myfriends )
But it will yield in terms of user_id ASC .
How can I make it search in DESC order of mutual_friends. ?
I am A i.e user_id = 1
i.e. Person with more mutual friends comes first
Please can anyone show me how can I do this? I have been stuck here for a long while. I searched for lots of thing but can't figure it out.

This query will take the reciprocity of relationships into account, so it doesn't matter if the relationship goes "From A to B" or "From B to A", it will still return the expected result. So given tables like this:
CREATE TABLE people
(`id` int, `name` varchar(1))
;
INSERT INTO people
(`id`, `name`)
VALUES
(1, 'A'),
(2, 'B'),
(3, 'C'),
(4, 'D'),
(5, 'E'),
(6, 'F')
;
CREATE TABLE friends
(`id` int, `personId1` int, `personId2` int)
;
INSERT INTO friends
(`id`, `personId1`, `personId2`)
VALUES
(1, 1, 2),
(2, 3, 1),
(3, 2, 4),
(4, 5, 2),
(5, 3, 4)
;
I believe this is set up as you described: A and B are friends, A and C are friends (notice the inverted relationship), B and D are friends, E and B are friends (another inverted relationship), and C and D are friends.
Assume the id of the person you want is in #personId:
SELECT StrangerId, COUNT(MutualFriendId) AS TotalMutualFriends
FROM
(SELECT
CASE WHEN f.personId2 = mf.friendId THEN f.personId1 ELSE f.personId2 END AS StrangerId,
CASE WHEN f.personId1 = mf.friendId THEN f.personId1 ELSE f.personId2 END AS MutualFriendId
FROM
(SELECT
CASE
WHEN personId1 = #personId THEN personId2
ELSE personId1
END AS friendId
FROM friends
WHERE personId1 = #personId OR personId2 = #personId) AS mf
INNER JOIN friends f
ON (personId1 != #personId AND personId2 = mf.friendId)
OR (personId1 = mf.friendId AND personId2 != #personId)
) AS totals
GROUP BY StrangerId
ORDER BY TotalMutualFriends DESC;
Results for #personId = 1 are:
StrangerId TotalMutualFriends
4 2
5 1
And here is a SQLFiddle to demonstrate (I couldn't get it to allow me to set up a variable, so there is a 1 in its place).

Something like this, perhaps:
Select user_id, friends.to_id, count(friend_of_friend.to_id)
from users left outer join
friends on users.user_id = friends.from_id left outer join
users as friend_user on friends.to_id = friend_user.user_id left outer join
friends as friend_of_friend on friend_user.user_id = friend_of_friend.from_id and friend_of_friend.to_id in (select to_id from friends where from_id = users.user_id)
Group by USER_ID, friends.to_id
Order by 3
Edit for clarity:
The logic of this query depends on joining the same table multiple times. The first two joins are pretty straight-forward, we are starting with a table of users, then joining in the friends table that links each user with all their friends. But then we join in the user table again, but this time using the "to" column - we are getting the user info for each friend. Since we can't have the same table name twice in a query, we give it an alias of "friend_user". Then we join the friends table again, based on the id in the friend_user table - this gives us all the friends of each original user's friends. Then we limit the friends of friends that we get back using the "Friend_of_friend.to_id in ..." to compare the friends of friends to a list of all of the original user's friends, which we bring in by a subquery - the section just after the "in" that is enclosed in parenthesis.

Related

assign json array (id's) with mysql tables PHP [duplicate]

I have a json field that stores a list of ids (not best practice here I know), I want to know if it's possible to use do operations on this JSON field and use them in the sql.
Below is a fictitious example of what I'm trying to achieve, is something like this doable?
CREATE TABLE user (
user_id INT,
user_name VARCHAR(50),
user_groups JSON
);
CREATE TABLE user_group (
user_group_id INT,
group_name VARCHAR(50)
);
INSERT INTO user_group (user_group_id, group_name) VALUES (1, 'Group A');
INSERT INTO user_group (user_group_id, group_name) VALUES (2, 'Group B');
INSERT INTO user_group (user_group_id, group_name) VALUES (3, 'Group C');
INSERT INTO user (user_id, user_name, user_groups) VALUES (101, 'John', '[1,3]');
With the above data I would like to fashion a query that gives me the results like this:
user_id | user_name | user_group_id | group_name|
-------------------------------------------------
101 | John | 1 | Group A
101 | John | 3 | Group C
Some psuedo style SQL I'm thinking is below, though I still have no clue if this is possible, or what JSON functions mysql offers I would use to achieve this
SELECT
u.user_id,
u.user_name,
g.user_group_id
g.group_name
FROM users u
LEFT JOIN user_group g on g.user_group_id in some_json_function?(u.user_groups)
Let me know if the question isn't clear.
With the help of Feras's comment and some fiddling:
SELECT
u.user_id,
u.user_name,
g.user_group_id,
g.group_name
FROM user u
LEFT JOIN user_group g on JSON_CONTAINS(u.user_groups, CAST(g.user_group_id as JSON), '$')
This appears to work, let me know if there's a better way.
Funny, I got to the opposite solution compared to Kyle's.
I wrote my query like this:
SELECT
u.user_id,
u.user_name,
g.user_group_id,
g.group_name
FROM user u
LEFT JOIN user_group g on JSON_UNQUOTE(JSON_EXTRACT(u.user_groups, '$')) = g.user_group_id;
It also works, and this solution doesn't need any transforming on the right side of the expression, this could provide a benefit in query optimizing in certain cases.
For arrays like ["1", "2", "3"] that values are in string type, JSON_SEARCH function is the way for your question:
SELECT
u.user_id,
u.user_name,
g.user_group_id
g.group_name
FROM users u
LEFT JOIN user_group g ON (JSON_SEARCH(u.user_groups, 'one', g.user_group_id))
JSON_CONTAINS function does not return true for integers as candidate parameter:
SELECT JSON_CONTAINS(CAST('["1", "2", "3"]' AS JSON), CAST(1 AS JSON), '$')
returns 0 (false). You need to change it to this:
SELECT JSON_CONTAINS(CAST('["1", "2", "3"]' AS JSON), CAST(CONCAT('"', 1, '"') AS JSON), '$')
But JSON_SEARCH can find the result:
SELECT JSON_SEARCH(CAST('["1", "2", "3"]' AS JSON), 'one', 1)
returns "$[0]" that means "true".
I have just tried the following and it worked in mysql 8.0.26:
-- index
ALTER TABLE user ADD KEY ( (CAST(user_groups -> '$' AS UNSIGNED ARRAY)) );
SELECT *
FROM user as a
left join user_group as t on t.user_group_id MEMBER OF (a.user_groups)
where t.group_name in ('Group A', 'Group C');
The query is based on the following select syntax:
SELECT * FROM t_json_arrays WHERE 3 MEMBER OF (c_array);
See examples on: https://saveriomiroddi.github.io/Storage-and-indexed-access-of-denormalized-columns-arrays-on-mysql-8.0-via-multi-valued-indexes/

Remove duplicates in MySQL table - set group_id when city_id is the same

I have table units in my database. In schema I have fields id, unit_id, group_id, city_id.
For simple I have 3 units:
(1, 1, 1, 1)
(2, 1, 2, 1)
(3, 1, 3, 2)
How can I remove useless groups id, when city id is the same. I have next result:
(1, 1, 1, 1)
(2, 1, 1, 1)
(3, 1, 3, 2)
I know how do this in PHP, but I think 'maybe MySQL has inbuild functions which i don't know' ;)
Regards
if I understand your question correctly you want to all group_id have same value from the same city_id. Basically your first table in question is what you have and the second one is desired result. If that's the case your query could look like this:
UPDATE table1
INNER JOIN (SELECT * FROM table1 GROUP BY city_id) AS tx
ON table1.city_id = tx.city_id
SET table1.group_id = tx.group_id;
Here is the SQL Fiddle to see how it's work.
If you want to completely remove values and to hold only distinct city_id then you can do that with query like this:
DELETE table1 FROM table1
INNER JOIN (SELECT * FROM table1 GROUP BY city_id) AS tx
ON table1.city_id = tx.city_id
WHERE table1.group_id != tx.group_id;
Here is SQL Fiddle for that!
In this case your result table will be without row with id 2...
GL!
If I understand correctly, you want to delete rows where group_id and city_id are equal? If so, it's very simple:
DELETE FROM units WHERE group_id = city_id
Okay, my solution:
UPDATE `ingame_units` INNER JOIN `ingame_groups` g1 ON `ingame_units`.`group_id`=g1.`id` LEFT JOIN `ingame_groups` g2 ON `ingame_units`.`group_id`<>g2.`id` AND g1.`city_id`=g2.`city_id` AND g1.`id`>g2.`id` AND g1.`game_id`=g2.`game_id` SET `ingame_units`.`group_id`=IFNULL(g2.`id`,g1.`id`)
Thanks one man to minus my post and don't try to help me. Regards :)

show results of multiple mysql tables and sort

I have 4 tables:
Table 1: Users
id
username
Table 2: Acts
act_id
act
user_id
act_score
act_date
Table 3: Votes
vote_id
act_id
user_voter_id
score_given
date_voted
Table 4: Comments
comment_id
comment
commenter_id
act_commented
date_commented
I want to show the contents of Acts Votes and Comments, based on User ID, combined in a list sorted in date order. Similar idea to Facebook's NewsFeed.
Sample output:
05-02-2014 10:00 Comment: "That's funny"
04-02-2014 12:30 Act Posted: "This is what I did"
04-02-2014 11:00 Comment: "Rubbish"
03-02-2014 21:00 Comment: "Looks green to me"
02-02-2014 09:00 Voted: +10 "Beat my personal best" by Cindy
01-02-2014 14:25 Act Posted: "Finally finished this darn website!"
I have tried to go down the create VIEW route to add all the required info to a table but
it was the wrong path. Now I'm not sure what to do!
Use UNION to combine separate queries. For example, to get the 10 most recent events across the three tables:
(
-- my acts
SELECT a.act_date timestamp,
'Act Posted' type,
a.act description,
u.username
FROM Acts a
JOIN Users u ON u.id = a.user_id
WHERE a.user_id = ?
ORDER BY a.act_date DESC
LIMIT 10
) UNION ALL (
-- votes on my acts
SELECT v.date_voted,
CONCAT('Voted ', v.score_given),
a.act,
u.username
FROM Votes v
JOIN Acts a USING (act_id)
JOIN Users u ON u.id = v.user_voter_id
WHERE a.user_id = ?
ORDER BY v.date_voted DESC
LIMIT 10
) UNION ALL (
-- comments on my acts
SELECT c.date_commented,
'Comment',
c.comment,
u.username
FROM Comments c
JOIN Acts a ON a.act_id = c.act_commented
JOIN Users u ON u.id = c.commenter_id
WHERE a.user_id = ?
ORDER BY c.date_commented DESC
LIMIT 10
)
ORDER BY timestamp DESC
LIMIT 10
first of all make id as a foreign key and use it in rest of the 3 tables while inserting data into those 3 tables.
like for Acts table,table structure should be like below.
Table 2: Acts
id //this is user id which is stored in the session while login.
act_id
act
user_id
act_score
act_date
The another thing to do is manage session for each and every user while he/she logged in.
Store user_id in the session for the further use like below.
session_start();
$_SESSION['user_id']=$_POST['ID'];
Then,fire select query for the particular table.I give you example of select query.
$sql="select * from Acts where id='".$_SESSION['id']."' ORDER BY act_date DESC";
$query=mysql_query($sql) or die("query failed");
Now, you will get result of Acts of particular user order by date.Then print it wherever you want.

MySQL Joining two rows, two tables, with the same fields

Hi Im new to MySQL and PHP, and this is my first post.. So please bear with me.
I am trying to add two tables and two rows from one table
friends
member_ID | friend_ID | status
and a user table
member_ID | username
What I am trying to do is combine both tables for a friend request, friends.member_ID is the user.member_ID sending the request, friends.member_ID, is the user.member_ID that is being requested, status is when they accept the request it will turn 0 to 1 which will make the relationship true.
so far, I have this for my query to display all these fields to show who has requested this person as a friend
SELECT users.member_ID, friends.member_ID, friends.friend_ID, friends.status
FROM `users` , `friends`
WHERE friends.status=0
AND users.member_ID = friends.member_ID
AND friends.member_ID = users.member_ID
AND users.member_ID = 6 (this will be $_SESSION[member_ID] when I add it to php)
I understand you can use an alias but I am a bit confused
Please help, my assignment is due tomorrow, and there is still so much to do.
Thanks
try this with join
SELECT users.member_ID, friends.member_ID, friends.friend_ID, friends.status
FROM `users`
INNER JOIN `friends`
ON users.member_ID = friends.member_ID
WHERE friends.status=0
AND users.member_ID = 6
Column alias - use AS(/as) after column name
users.member_ID as uId, friends.member_ID as fId
Table alias - define after table name
users u , friends f
If using table aliases, you can use them when selecting your column names
u.member_ID as uId, f.member_ID as fId
http://dev.mysql.com/doc/refman/4.1/en/problems-with-alias.html
SELECT u.member_ID as uId, f.member_ID as fId, f.friend_ID as ffId, f.status as fStatus
FROM `users` u
INNER JOIN `friends` f
ON u.member_ID = f.member_ID
WHERE f.status=0
AND u.member_ID = 6
Aliases are useful when you are selecting 2 or more columns with the same name from multiple tables - users.member_ID, friends.member_ID. Instead of using an ambiguous $row['member_ID'] or having to use $row[0]/$row[1], you can use $row['uID']/$row['fID']

Join tables with comma values

I have a hard nut to crack with joing 3 tables.
I have a newsletter_items, newsletter_fields and newsletter_mailgroups which I want to be joined to get a list of newsletters.
The newsletter_items contains the fields:
letter_id, letter_date, receivers, template, status
That can look like
1, 1234567899, 1,2 (comma separated), standard.html, 1
newsletter_fields contains the fields:
field_uid, field_name, field_content, field_letter_uid
That can look like
1, letter_headline, A great headline, 1
where field_letter_uid is the newsletter for which the field belongs to.
and newsletter_mailgroups contains the fields:
mailgroup_id, mailgroup_name, number_of_members
That can look like
1, Group1, 233
2, Group2, 124
3, Group3, 54
What I want is to combine these 3 tables to that I can get a list of all the newsletter like this:
Letter date | Letter headline | Receivers | Status
2008-01-01 12:00:00 | A great headline | Group1, Group 2 | 1
So in short I want my SQL query to join the 3 tables and in that process select the receivers from the mailgroup table and display them comma separated like Group1, Group 2
This what I got now
SELECT A.*, B.* FROM newsletter_items A, newsletter_fields B, WHERE B.field_letter_uid = A.letter_id AND field_name = 'letter_headline' AND A.template = '". $template ."';
But I can't seem to figure out how to get the mailgroups into that.
I recommend that you make your joins explicit.
It makes it easier to debug your query and to change inner with left joins.
There is absolutely never a good reason to use SQL '89 implicit join syntax.
SELECT ni.*
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
Regarding your database design.
I recommend you normalize your database, that means that you move the comma separated fields into a different table.
So you make a table receivers
Receivers
----------
id integer auto_increment primary key
letter_id integer not null foreign key references newsletter_items(letter_id)
value integer not null
You then remove the field receiver from the table newsletter_items
Your query then changes into:
SELECT ni.*
, group_concat(r.value) as receivers
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
LEFT JOIN receiver r ON (r.letter_id = ni.letter_id)
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
This change should also speed up your query significantly.
If it's allowed, why don't you create a new table called newsletter_item_receivers where you could store letter_id, receiver_id fields?
Having comma separated values in a field like this usually means you're missing a table :)
Edit:
By using CSV, you are making your life miserable when you want to retrieve an answer to "give me all newsletters that receiver_id=5 receives" :)
Here's a good answer to a similar question on SO: Comma separated values in a database field
Edit2:
If I understand your table relationships correctly then it would be something like this:
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items_receivers b
LEFT OUTER JOIN newsletter_items a ON (a.letter_id = b.letter_id)
LEFT OUTER JOIN newsletter_mailgroups m ON (m.mailgroup_id = b.receiver_id)
NOTE! This query WILL NOT return a newsletter when there are no receivers of that newsletter.
If you need that functionality you can try something like this:
SELECT
x.letter_date,
y.mailgroup_name,
x.status
FROM (
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items a
LEFT OUTER JOIN newsletter_items_rec b ON (b.letter_id = a.letter_id)) x
LEFT OUTER JOIN newsletter_mailgroups y ON (y.mailgroup_id = x.receiver_id)
I don't have access to SQL right now so I might have made some syntax errors (hopefully not logical ones :)).
As for why we are doing it like this, as #Konerak pointed out, you'd be well advised to read up on database normalization and why it's important.
You can start with this article from about.com, just glanced over it seems an OK read
http://databases.about.com/od/specificproducts/a/normalization.htm
Also, it would be good if you'd keep fields names the same across multiple tables.
For example you have letter_id in newsletter_items, but you have field_letter_uid in newsletter_fields. Just a thought :)
Try to use
SELECT A.*, B.*, group_concat(C.mailgroup_name SEPARATOR ',')
FROM newsletter_items A, newsletter_fields B, newsletter_mailgroups C
WHERE B.field_letter_uid = A.letter_id
AND field_name = 'letter_headline'
AND A.template = '". $template ."'
and find_in_set(c.mailgroup_id, A.receivers)
group by A.letter_id;

Categories