sql query to get friends based on email - mysql

I have sample data that looks like this
| users |
| user_id | email |
|---------|--------------------------|
| 1 | test#example.com |
| 2 | Kanchhi#example.com |
| 3 | modi#example.com |
| 4 | andy#example.com |
| 5 | maya#example.com |
| 6 | jetli#example.com |
| 7 | john#example.com |
| user_relations |
| user_relation_id | requestor_user_id | receiver_user_id | friend_status |
|------------------|-------------------|------------------|---------------|
| 1 | 2 | 4 | 1 |
| 2 | 2 | 6 | 1 |
| 3 | 2 | 7 | 1 |
| 4 | 5 | 2 | NULL |
| 5 | 5 | 7 | NULL |
| 6 | 7 | 2 | NULL |
| 7 | 7 | 4 | 1 |
| 8 | 7 | 5 | 1 |
| 9 | 7 | 6 | 1 |
| 10 | 4 | 2 | 1 |
| 11 | 4 | 3 | 1 |
| 12 | 4 | 5 | 1 |
| 13 | 4 | 6 | 1 |
| 14 | 4 | 7 | 1 |
If input is these two emails:
Kanchhi#example.com, john#example.com
Then my expected expected output is this (order does not matter):
andy#example.com
jetli#example.com
In the above example, the friends of user id 2 is user ids (4, 6, 7) and friend of user id 7 is user ids (2, 4, 5, 6). So the mutual friend of user id 2 and 7 is 4 and 6. I need email address of mutual user id.
Another example input is:
andy#example.com, john#example.com
Then expected output is this:
jetli#example.com
Kanchhi#example.com
maya#example.com`
In the above example, friend of user id 4 is (2, 6, 5, 3, 2) and friend of user id 7 is (6, 5, 2, 4). So mutual friend user id will be 2, 6, 5. I need email address of these user id in output.
Query 1- I tried but got wrong result:
SELECT u.email
FROM user_relations r
LEFT JOIN users u ON r.requestor_user_id = u.user_id
LEFT JOIN users z ON r.receiver_user_id = z.user_id
where u.email in ('Kanchhi#example.com','john#example.com') or
z.email in ('Kanchhi#example.com','john#example.com')
and r.friend_status = 1
group by u.email
having count(u.email ) > 1
Results- but not correc:
| email |
|---------------------|
| andy#example.com |
| john#example.com |
| Kanchhi#example.com |
How to get this?

The crux of the problem is building a list of users that are friends of Kanchi and John or have them as friends. Then count those users that appear in the list twice:
-- SELECT email FROM users WHERE userid IN (
SELECT friendid
FROM (
SELECT requestor_user_id AS userid, receiver_user_id AS friendid
FROM user_relations
WHERE friend_status = 1 AND requestor_user_id IN (
SELECT user_id
FROM users
WHERE email IN ('Kanchhi#example.com','john#example.com')
)
UNION ALL
SELECT receiver_user_id, requestor_user_id
FROM user_relations
WHERE friend_status = 1 AND receiver_user_id IN (
SELECT user_id
FROM users
WHERE email IN ('Kanchhi#example.com','john#example.com')
)
) AS X
GROUP BY friendid
HAVING COUNT(DISTINCT userid) = 2
Matching the results with users is trivial. For your sample input the result is:
4 Andy andy#example.com ashutosh 2019-01-11 13:34:05
6 jetli jetli#example.com ashutosh 2019-01-11 13:34:05

You can start from the user, then join the relations.
Then group on the receiver & requestor that are different from the user.
SELECT
case when u.user_id = r.receiver_user_id then requestor.email else receiver.email end as friend_email
FROM users u
JOIN user_relations r
ON (r.requestor_user_id = u.user_id OR r.receiver_user_id = u.user_id)
AND r.friend_status = 1
LEFT JOIN users requestor ON requestor.user_id = r.requestor_user_id
LEFT JOIN users receiver ON receiver.user_id = r.receiver_user_id
WHERE u.email in ('Kanchhi#example.com','john#example.com')
GROUP BY friend_email
HAVING COUNT(DISTINCT u.user_id) > 1
Result:
friend_email
----------------
andy#example.com
jetli#example.com

You could use this and use index in join coulmn for performance
SELECT DISTINCT u.email
FROM
(
SELECT r.receiver_user_id AS id, u.email
FROM user_relations r
INNER JOIN users u
ON r.requestor_user_id = u.user_id
WHERE r.friend_status = 1 AND u.email = 'Kanchhi#example.com'
UNION ALL
SELECT r.requestor_user_id AS id, u.email
FROM user_relations r
INNER JOIN users u
ON r.receiver_user_id = u.user_id
WHERE r.friend_status = 1 AND u.email = 'Kanchhi#example.com'
) k
INNER JOIN
(
SELECT r.receiver_user_id AS id, u.email
FROM user_relations r
INNER JOIN users u
ON r.requestor_user_id = u.user_id
WHERE r.friend_status = 1 AND u.email = 'john#example.com'
UNION ALL
SELECT r.requestor_user_id AS id, u.email
FROM user_relations r
INNER JOIN users u
ON r.receiver_user_id = u.user_id
WHERE r.friend_status = 1 AND u.email = 'john#example.com'
) j
ON k.id = j.id
INNER JOIN users u
ON k.id = u.user_id
AND u.email NOT IN ('Kanchhi#example.com','john#example.com');

Try this
SELECT usu.email from users usu where usu.user_id in (
select r.receiver_user_id
from sov.user_relations r
where r.requestor_user_id in (
SELECT u.user_id from users u where u.email in ('Kanchhi#example.com','john#example.com')
)
and r.friend_status = 1
group by r.receiver_user_id
having count(r.receiver_user_id) > 1
);

you can create extra function like below code
BEGIN
DECLARE limitCount INT DEFAULT 0;
DECLARE counter INT DEFAULT 0;
DECLARE res INT DEFAULT 0;
DECLARE temp TEXT;
SET limitCount = 1 + LENGTH(inputList) - LENGTH(REPLACE(inputList, ',',''));
simple_loop:LOOP
SET counter = counter + 1;
SET temp = SUBSTRING_INDEX(SUBSTRING_INDEX(inputList,',',counter),',',-1);
SET res = FIND_IN_SET(temp,targetList);
IF res > 0 THEN LEAVE simple_loop; END IF;
IF counter = limitCount THEN LEAVE simple_loop; END IF;
END LOOP simple_loop;
RETURN res;
END
AND USE THIS FUNCTION LIKE THIS
find_in_set_extra('Kanchhi#example.com, john#example.com','john#example.com') OR WHAT EVER YOUR INPUTS AND OUTPUTS.

Related

Improve query to join other tables depending on value without union

I have a table containing the following data:
+-----------------+--------------+----------------------+------------+--------------------+-----------+------+---------+---------+-----+------------------------+---------------------+
| notification_id | from_user_id | from_user_auth_level | to_user_id | to_user_auth_level | status_id | type | subject | message | url | timestamp_inserted_utc | timestamp_read_utc |
+-----------------+--------------+----------------------+------------+--------------------+-----------+------+---------+---------+-----+------------------------+---------------------+
| 1 | NULL | NULL | 1 | 1 | 0 | 2 | test | test | url | 2010-10-10 00:00:00 | 2011-10-10 00:00:00 |
| 2 | 2 | 5 | 1 | 1 | 0 | 2 | test | test | url | 2010-10-10 00:00:00 | 2011-10-10 00:00:00 |
| 3 | 3 | 5 | 1 | 1 | 0 | 2 | test | test | url | 2010-10-10 00:00:00 | 2011-10-10 00:00:00 |
| 4 | 2295 | 4 | 1 | 1 | 0 | 2 | test | test | url | 2010-10-10 00:00:00 | 2011-10-10 00:00:00 |
| 5 | 10 | 1 | 1 | 1 | 0 | 2 | test | test | url | 2010-10-10 00:00:00 | 2011-10-10 00:00:00 |
+-----------------+--------------+----------------------+------------+--------------------+-----------+------+---------+---------+-----+------------------------+---------------------+
And then I have some other tables like 'users', 'companies', 'organizations', ... etc.
I need to be able to get the username, gender and image of every notification (based on the from_user_id and from_user_auth_level).
But the problem resides in the fact, that this info resides in different places, depending on what the user_auth_level is.
For example: if my user is a "regular" user, his auth_level will be 1. And the image will reside in my "users" table, and the gender is applicable.
But if the user has auth_level == 5, it means he is an organization. In this case, gender is not applicable, and the image resides in the "organization" table, this needs to be linked via users to user_roles and then to the organization.
And this goes on for every user type, they all require different joins.
I have created a working query, but this uses UNION's everywhere, and I have read that it is not the best to use for performance reasons, so i'm hoping someone can guide me to improving this query with performance in mind:
SELECT n.*, NULL as username, NULL as gender, NULL as picture
FROM notification as n
WHERE n.from_user_auth_level IS NULL
AND n.to_user_id = $userid
UNION
SELECT n.*, u.username, u.gender as gender, u.profile_picture as picture
FROM notification as n
LEFT JOIN users AS u ON n.from_user_id = u.user_id
WHERE n.from_user_auth_level = 1
AND n.to_user_id = $userid
UNION
SELECT n.*, u.username, NULL as gender, c.logo as picture
FROM notification as n
LEFT JOIN users AS u ON n.from_user_id = u.user_id
LEFT JOIN user_companies AS uc on u.user_id = uc.user_id
LEFT JOIN company as c on uc.company_id = c.company_id
WHERE n.from_user_auth_level = 4
AND n.to_user_id = $userid
UNION
SELECT n.*, u.username, NULL as gender, o.logo as picture
FROM notification as n
LEFT JOIN users AS u ON n.from_user_id = u.user_id
LEFT JOIN user_roles as ur on u.user_id = ur.user_id
LEFT JOIN organization as o on ur.org_id = o.org_id
WHERE n.from_user_auth_level = 5
AND n.to_user_id = $userid
UNION
SELECT n.*, u.username, NULL as gender, o.logo as picture
FROM notification as n
LEFT JOIN users AS u ON n.from_user_id = u.user_id
LEFT JOIN user_roles as ur on u.user_id = ur.user_id
LEFT JOIN organization as o on ur.org_id = o.org_id
WHERE n.from_user_auth_level = 7
AND n.to_user_id = $userid
UNION
SELECT n.*, u.username, NULL as gender, NULL as picture
FROM notification as n
LEFT JOIN users AS u ON n.from_user_id = u.user_id
WHERE n.from_user_auth_level = 9
AND n.to_user_id = $userid"
After I get this result, I use PHP to order the results based on the timestamp_inserted_utc, since it's not possible to get the correct results with the UNION.
I'd use the notification table as base and used conditional outer joins as:
select
n.*,t1.gender, t2.orgNo
from
notifications n
left outer join table1 t1 on (n.auth=1 and more join)
left outer join table2 t2 on (n.auth=2 and more..)
You will have more columns but their name would make sense and you may merge at application level.
Suggested indexes (most are "covering"):
n: (from_user_auth_level, to_user_id, from_user_id)
u: (user_id, username, profile_picture, gender)
o: (org_id, logo)
ur: (org_id, user_id)
c: (company_id, logo)
uc: (company_id, user_id)
(They may even speed up Teson's Answer.)

MySQL count based on condition from a different table

I have the following tables.
Table : types
--------------------
id | type
--------------------
1 | AA
--------------------
2 | BB
--------------------
3 | AA
--------------------
4 | BB
--------------------
Table : users
--------------------
id | username
--------------------
1 | abc
--------------------
2 | bcd
--------------------
3 | cde
--------------------
4 | def
--------------------
Table : methods
---------------------------------
id | user_id | details | type_id
---------------------------------
1 | 1 | detail_1 | 1
---------------------------------
2 | 1 | detail_2 | 3
---------------------------------
3 | 1 | detail_3 | 1
---------------------------------
4 | 1 | detail_4 | 3
---------------------------------
5 | 2 | detail_3 | 1
---------------------------------
6 | 2 | detail_5 | 2
---------------------------------
7 | 2 | detail_6 | 4
---------------------------------
8 | 2 | detail_2 | 3
---------------------------------
9 | 1 | detail_2 | 3
---------------------------------
10 | 1 | detail_2 | 3
---------------------------------
Desired Result :
---------------------------------------------------
UserName | No_of_AA_details | No_of_BB_details |
---------------------------------------------------
abc | 4 | 0 |
---------------------------------------------------
bcd | 2 | 2 |
---------------------------------------------------
I need to get the count of distinct details based on the type from types table.
I have tried this queries but max I am getting is all the counts and not the distinct values.
SELECT u.username,
CASE WHEN t.type = 'AA' THEN count(distinct m.details) END AS No_of_AA_details,
CASE WHEN t.type = 'BB' THEN count(distinct m.details) END AS No_of_BB_details
FROM users as u inner join methods as m on u.id = m.user_id inner join types as t on t.id = m.type_id
GROUP BY m.user_id
SELECT u.username,
SUM(t.type = 'AA') AS No_of_AA_details,
SUM(t.type = 'AA') AS No_of_BB_details
FROM users as u inner join methods as m on u.id = m.user_id inner join types as t on t.id = m.type_id
GROUP BY m.user_id
Any suggestions are welcome.
I can't test it right know but i think you had a good idea, can you try :
SELECT u.username,
m.user_id,
CASE
WHEN t.type = 'AA' THEN 1
ELSE 0
END AS No_of_AA_details,
CASE
WHEN t.type = 'BB' THEN 1
ELSE 0
END AS No_of_BB_details
FROM users as u
INNER JOIN methods as m on u.id = m.user_id
INNER JOIN types as t on t.id = m.type_id
and now you just need to do the sum :
SELECT u.username,
m.user_id,
SUM (CASE
WHEN t.type = 'AA' THEN 1
ELSE 0
END ) AS No_of_AA_details,
SUM (CASE
WHEN t.type = 'BB' THEN 1
ELSE 0
END ) AS No_of_BB_details
FROM users as u
INNER JOIN methods as m on u.id = m.user_id
INNER JOIN types as t on t.id = m.type_id
GROUP BY u.username, m.user_id

Select certain value only

I have 2 table, but I wanted to query the 'rejected' status only,
means I need query the result that the user has only rejected status, instead of having approve & reject, or approve in submissions table
Users Table
-----------
id | name
-----------
1 | John
2 | Doe
3 | Testing
4 | Sample
Submission Table
-------------------------------
id | user_id | title | status
-------------------------------
1 | 1 | title1 | approved
2 | 1 | title2 | rejected
3 | 2 | title3 | approved
4 | 2 | title4 | approved
5 | 3 | title5 | rejected
6 | 3 | title6 | rejected
7 | 3 | title7 | rejected
8 | 4 | title8 | approved
9 | 4 | title9 | approved
10| 4 | title10| rejected
11| 4 | title11| rejected
Below is the result I wanted to achieve :
But I outer join the result query by 'rejected' only but still have some 'approved' result by the users.
but with above query, I'd this result.
What I wanted to query is , query the submissions just have status 'rejected' only, fully ignore the 'approved' , 'approve or reject' result.
I found a solution already which is using WHERE NOT EXISTS to filter the approved result in submission
SELECT u.id AS user_id,s.*, u.name
FROM submissions s
LEFT OUTER JOIN users u
ON s.user_id = u.id
WHERE NOT EXISTS (
SELECT USER_ID
FROM submissions tmp
WHERE tmp.User_ID = s.User_ID
AND tmp.status = 'approved'
)
AND STATUS = 'rejected'
Why did you use left outer join ? I believe simple join will get you the result ..
SELECT u.id AS user_id,s.*, u.name
FROM submissions s
JOIN users u
ON s.user_id = u.id AND s.status = 'rejected'
OR
SELECT u.id AS user_id,s.*, u.name
FROM submissions s
JOIN users u
ON s.user_id = u.id
WHERE s.status = 'rejected'
create table users
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL UNIQUE
);
INSERT INTO users VALUES
(1,'John'),
(2,'Doe'),
(3,'Testing'),
(4,'Sample');
create table submissions
(id int not null auto_increment primary key
,userid int not null
,title varchar(12) not null
,status varchar(12) not null
);
insert into submissions values
( 1,1,'title1','approved'),
( 2,1,'title2','rejected'),
( 3,2,'title3','approved'),
( 4,2,'title4','approved'),
( 5,3,'title5','rejected'),
( 6,3,'title6','rejected'),
( 7,3,'title7','rejected'),
( 8,4,'title8','approved'),
( 9,4,'title9','approved'),
(10,4,'title10','rejected'),
(11,4,'title11','rejected');
SELECT u.name
, x.*
FROM submissions x
JOIN users u
ON u.id = x.userid
LEFT
JOIN submissions y
ON y.status = 'approved'
AND y.userid = x.userid
WHERE x.status = 'rejected'
AND y.userid IS NULL;
+---------+----+--------+--------+----------+
| name | id | userid | title | status |
+---------+----+--------+--------+----------+
| Testing | 5 | 3 | title5 | rejected |
| Testing | 6 | 3 | title6 | rejected |
| Testing | 7 | 3 | title7 | rejected |
+---------+----+--------+--------+----------+
http://sqlfiddle.com/#!9/1e9da/1
You can use the NOT IN(your exclude criteria) format as below.
SELECT u.id AS user_id,s.*, u.name
FROM submissions s
LEFT OUTER JOIN users u
ON s.user_id = u.id
WHERE s.user_id NOT IN(
SELECT tmp.user_id
FROM submissions tmp
WHERE tmp.status = 'approved'
)
AND STATUS = 'rejected'

MySQL join and sum issue / combine queries

I have created a points example to demonstrate my issue.
A members can reward other members points, I am trying to build a query which will display the points a user has and points user has given.
Database Example
Members,
+----+----------+
| id | username |
+----+----------+
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
+----+----------+
Points,
+-------+--------+--------+
| IDFor | IDFrom | Pointz |
+-------+--------+--------+
| 1 | 2 | 5 |
| 1 | 2 | 5 |
| 3 | 1 | 2 |
+-------+--------+--------+
The return I am looking for is,
+-----------+--------+-------+
| username | Pointz | Given |
+-----------+--------+-------+
| user1 | 10 | 2 |
| user2 | 0 | 10 |
| user3 | 2 | 0 |
+-----------+--------+-------+
Both my queries return,
+-----------+--------+-------+
| username | Pointz | Given |
+-----------+--------+-------+
| user1 | 10 | 4 |
| user2 | 0 | 10 |
| user3 | 2 | 0 |
+-----------+--------+-------+
http://sqlfiddle.com/#!2/32ae6/6
SELECT a.`username`, sum(a.`Pointz`), sum(b.`Pointz`) FROM
(SELECT * FROM `members`
LEFT JOIN `Example`.`Points` AS p ON `members`.`id` = p.`IDFor` ) AS a
LEFT JOIN
(SELECT * FROM `members`
LEFT JOIN `Example`.`Points` AS n ON `members`.`id` = n.`IDFrom` ) AS b
ON a.id = b.id
GROUP BY a.`id`
http://sqlfiddle.com/#!2/32ae6/7
SELECT `members`.`username`,sum(p.`Pointz`),sum(n.`Pointz`)
FROM `members`
LEFT JOIN `Example`.`Points` as p ON p.`IDFor` = `members`.`id`
LEFT JOIN `Example`.`Points` as n ON n.`IDFrom` = `members`.`id`
GROUP BY `members`.`id`
Seems to be a common question that pops up but have not found a solution, all my other attempts from similar questions have not ended well, Thanks all.
This is what u want
select a.username, ifnull(Pointz, 0) Pointz, ifnull(Given, 0) Given from
(SELECT id, `username`, sum(`Pointz`) Pointz FROM `members`
LEFT JOIN `Points` ON `members`.`id` = `Points`.`IDFor` group by id) a
left join
(SELECT id, `username`, sum(`Pointz`) Given FROM `members`
LEFT JOIN `Points` ON `members`.`id` = `Points`.`IDFrom` group by id) b
on a.id = b.id
Try:
SELECT m.username,
COALESCE( pr.preceived, 0 ) as Pointz,
COALESCE( pg.pgiven, 0 ) as Given
FROM members m
LEFT JOIN ( SELECT IDFor, sum(pointz) as preceived
FROM Points
GROUP BY IDFor ) pr
ON m.id = pr.IDFor
LEFT JOIN ( SELECT IDFrom, sum(pointz) as pgiven
FROM Points
GROUP BY IDFrom ) pg
ON m.id = pg.IDFrom
http://sqlfiddle.com/#!2/32ae6/48
Try this:
SELECT m.`username`,
ifnull((SELECT sum(`Pointz`) FROM Points p
WHERE p.IDFor = m.id ), 0),
ifnull((SELECT sum(`Pointz`) FROM Points p
WHERE p.IDFrom = m.id ), 0)
FROM Members m
SQLFiddle

How to write a MySQL Join query

I've two tables.
users:
uid | city | username | flag |
10 | New York | john | 1 |
14 | Tokyo | kawasaki | 1 |
15 | Tokyo | coder | 1 |
groupmember:
id | uid | groupid |
1 | 10 | 16 |
2 | 14 | 16 |
3 | 15 | 21 |
The 'uid' in both the tables are the same.
I want to select all users who are in city "tokyo" who are also in the group with groupid "16" .
So the query resutl should be (in this case)
14 | Tokyo | kawasaki | 1 |
SELECT u.uid, u.city, u.username, u.flag
FROM users u
JOIN groupmember g ON u.uid = g.uid
WHERE u.city = 'Tokyo'
AND g.groupid = 16;
select u.* from users u join groupmember gm on u.uid = gm.uid
where u.city='Tokyo' and gm.groupid=16
SELECT * FROM
users INNER JOIN groupmember
ON users.uid = groupmember.uid
AND groupmember.groupid = 16
AND users.city = 'Tokyo'
SELECT u.uid, u.city, u.username, u.flag
FROM users u, groupmember g
WHERE u.uid = g.uid
AND u.city = 'Tokyo'
AND g.groupid = 16;