I try to make a sql query to get all my followers except me.
I have the following tables:
users
| id_user | username |
| 1 | xaxa |
| 2 | toto |
| 3 | bubu |
| 4 | yiyi |
| 5 | pepe |
| 6 | sisi |
| 7 | fifi |
| 8 | mama |
| 9 | juju |
| 10 | cece | => me
friends
| id_friend | id_user | id_user_to |
| 1 | 10 | 1 |
| 2 | 2 | 10 |
| 3 | 2 | 1 |
| 4 | 6 | 3 |
| 5 | 2 | 9 |
| 6 | 6 | 7 |
| 7 | 5 | 3 |
| 8 | 10 | 5 |
| 9 | 9 | 8 |
| 10 | 8 | 10 |
I want to have this:
my friends
| id_user | name |
| 1 | xaxa |
| 2 | toto |
| 5 | pepe |
| 8 | mama |
actually I have id_user 10 (me) in the result with this query =>
SELECT id_user, name
FROM `users`
WHERE id_user NOT IN (
SELECT `id_user` FROM `friends`
WHERE ( `id_user` = 10 OR `id_user_to` = 10 ))
OR id_user NOT IN (
SELECT `id_user_to` FROM `friends`
WHERE ( `id_user` = 10 OR `id_user_to` = 10 ))
GROUP BY `id_user`
The solution is a union of two simple joins:
SELECT u.id_user, u.username name
FROM friends f
JOIN users u ON u.id_user = f.id_user_to
WHERE f.id_user = 10
UNION
SELECT u.id_user, u.username
FROM friends f
JOIN users u ON u.id_user = f.id_user
WHERE f.id_user_to = 10
Note that the keyword UNION removes duplicates from the result, so no need to code anything special to handle the case when there's a friend link in both directions between two users (FYI, UNION ALL retains duplicates).
Because at most one index is used per table per query, by splitting the query into two parts, if indexes are created on the user id columns of the friends table, this query will scale well (to millions of users)
There was no need to code anything to handle an "except me" condition, unless you have a row in the friends table for you being your own friend, which you don't.
SqlFiddle: http://sqlfiddle.com/#!9/7cbb3/4
This should do:
SELECT u.id_user, u.username name
FROM `friends` f
JOIN `users` u
ON u.id_user = f.id_user_to and f.id_user = 10
or u.id_user = f.id_user and f.id_user_to = 10
ORDER BY u.id_user
SqlFiddle: http://sqlfiddle.com/#!9/7cbb3/1
So essentially you need to get users who are related in either direction (id_user -> id_user_to OR id_user_to -> id_user)
You can do either one of those with these queries:
SELECT friends.id_user, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user_to = 10
SELECT friends.id_user_to, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user = 10
But you want both sides. One way to do it is to do both queries and UNION them together. You could do it like this whilst also adding in the names
SELECT friends.id_user, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user_to = 10
UNION
SELECT friends.id_user_to, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user = 10
It's also worth noting that the UNION will only show you distinct rows so if you have users in both directions (for example 1 -> 10 and 10 -> 1) they will not show twice.
Related
i have a following tables in MySQL database:
+------------------------+
| Users |
+----+--------+----------+
| id | name | role |
+----+--------+----------+
| 1 | Martin | admin |
+----+--------+----------+
| 2 | George | admin |
+----+--------+----------+
| 3 | John | employee |
+----+--------+----------+
+-------------------------+
| Forms |
+----+--------------------+
| id | type |
+----+--------------------+
| 10 | marketing_form |
+----+--------------------+
| 11 | client_survey_form |
+----+--------------------+
| 12 | client_survey_form |
+----+--------------------+
+---------------------------------------------+
| UsersAssignToForms |
+----+---------+---------+--------------------+
| id | user_id | form_id | additional_comment |
+----+---------+---------+--------------------+
| 20 | 1 | 10 | Lorem ipsum... |
+----+---------+---------+--------------------+
| 21 | 2 | 10 | Lorem ipsum.... |
+----+---------+---------+--------------------+
| 22 | 3 | 10 | null |
+----+---------+---------+--------------------+
| 23 | 3 | 11 | null |
+----+---------+---------+--------------------+
I would like to have result:
+---------+---------+------------+--------------------+--------------------+
| user_id | form_id | first_name | form_type | additional_comment |
+---------+---------+------------+--------------------+--------------------+
| 1 | 10 | Martin | marketing_form | Lorem ipsum... |
+---------+---------+------------+--------------------+--------------------+
| 3 | 11 | John | client_survey_form | null |
+---------+---------+------------+--------------------+--------------------+
| null | 12 | null | client_survey_form | null |
+---------+---------+------------+--------------------+--------------------+
First of all i would like to limit number of users returned from join query (one user per one form). If user with admin role is assigned to form i would like to display this user (prioritize admin role over employee role) and limit number of returned users to 1, if admin is not assign, but employee is assigned query should return this user, if no-one is assign query should return nulls (left or right join probably).
I saw this question on stackoverflow - MySQL JOIN with LIMIT 1 on joined table, but unfortunately first answer has n+1 issue and rest of answers was made with simple one join. For my purposes i need to join more tables but wouldn't like to design this tables above to clarify what i would like to achieve, but it's very important.
So my query will looks like probably:
SELECT u.id, f.id, u.name, f.type, uf.additional_comment, [more selects from other tables...] FROM Forms as f
LEFT JOIN Users as u ON ......
INNER JOIN UsersAssignToForms as uf ON .....
[here i would like to put more and more inner joins.....]
In MySql >= 8.0 you can number the rows using some criteria (for each Form starting from one and order by u.role ASC and u.id ASC), then you can filter rows with number one:
WITH sq AS (SELECT u.id AS user_id, f.id AS form_id, u.name, f.type, uf.additional_comment,
ROW_NUMBER() OVER (PARTITION BY f.id ORDER BY u.role ASC, u.id ASC) AS num
FROM Forms AS f
LEFT JOIN UsersAssignToForms AS uf ON f.id = uf.form_id
LEFT JOIN Users AS u ON u.id = uf.user_id)
SELECT *
FROM sq
WHERE num = 1;
Before MySql 8.0 you can try something like this (the idea is the same but with different implementation):
SELECT sq2.user_id, sq2.form_id, sq2.name, sq2.type, sq2.additional_comment
FROM (
SELECT
sq1.*,
#row_number:=CASE WHEN #form_id = sq1.form_id THEN #row_number + 1 ELSE 1 END AS num,
#form_id:= sq1.form_id
FROM (SELECT u.id AS user_id, f.id AS form_id, u.name, f.type, uf.additional_comment
FROM Forms AS f
LEFT JOIN UsersAssignToForms AS uf ON f.id = uf.form_id
LEFT JOIN Users AS u ON u.id = uf.user_id
ORDER BY f.id ASC, u.role ASC, u.id ASC) AS sq1
ORDER BY sq1.form_id) AS sq2
WHERE sq2.num = 1;
So i have three tables:
Users
+-------+-----+----+
| id | val1|val2|
+-------+-----+----+
| 1 | 1 |3 |
| 2 | 2 |5 |
| 3 | 4 |7 |
+-------+-----+----+
UsersData
+----+--------------+------------+-----|
| id | users_id | created_at | gold|
+----+--------------+------------+-----|
| 9 | 1 |121454561212| 14 |
| 10| 1 |131454561212| 2 |
| 11| 2 |111454561212| 99 |
+----+--------------+------------+-----+
Extra
+----+------------+-----|
| id | users_id | val4|
+----+------------+-----|
| 1 | 1 | 5 |
| 2 | 1 | 6 |
| 3 | 1 | 7 |
+----+------------+-----+
So what i wish to achieve(in a single query) is to get a single row result for user with id = 1, that holds:
everything from Users Table
gold value of the most recent entry for that user (users_id = 1, created_at = MAX)
biggest val4 from the Extra table, where users_id = 1
So the result row would look like this:
+-------+-----+----+-----+----+
| id | val1|val2|gold |val4|
+-------+-----+----+-----+----|
| 1 | 1 |3 | 2 | 7 |
------------------------------+
I can get The first part done with
SELECT Users.id, Users.val1, Users.val2, UsersData.gold
FROM UsersData
LEFT JOIN Users ON UsersData.users_id = Users.id
WHERE Users.id = 1
ORDER BY UsersData.created_at DESC
LIMIT 1
and the second part with
SELECT MAX(Distances.distance) AS maxdistance FROM Distances WHERE Distances.users_id = 1
But i can't combine them no matter how i try... I would really like to have this done in single query, obviously i can do it with multiple - but i believe it is just my lack of mysql skills that is the issue here.
Thanks!
Just use subquery:
SELECT Users.id, Users.val1, Users.val2, UsersData.gold,
(SELECT MAX(Distances.distance) FROM Distances WHERE Distances.users_id = Users.id) AS maxdistance
FROM UsersData
RIGHT JOIN Users ON UsersData.users_id = Users.id
WHERE Users.id = 1
ORDER BY UsersData.created_at DESC
LIMIT 1
This is subquery connected by Users.id:
SELECT MAX(Distances.distance) FROM Distances WHERE Distances.users_id = Users.id) AS maxdistance
I would use subqueries like this:
select u.*,
(select ud.gold
from userdata ud
where ud.users_id = u.id
order by ud.created_at desc
limit 1
) as most_recent_gold,
(select max(e.val4)
from extra e
where e.users_id = u.id
) as max_val4
from users u
where u.id = 1 ;
I want to display the posts of people who I follow
The 3 tables I have are:
Users:
+---------+------+
| id_user | name | last_logout
+---------+------+
| 1 | A | 22-02-2018 00:00:10
| 2 | B |
| 3 | C |
| 4 | D |
| 5 | E |
+---------+------+
Community:
+-------------+-------------+
| id_follower | id_followed |
+-------------+-------------+
| 1 | 2 |
| 1 | 3 |
| 1 | 5 |
+-------------+-------------+
Posts:
+---------+--------------+---------------+
| id_post | id_user_post | post | date
+---------+--------------+---------------+
| 1 | 2 | hi |
| 2 | 3 | hello |
| 3 | 5 | hey you |
| 4 | 4 | come on |
| 5 | 5 | where are you | 22-02-2018 00:01:00
+---------+--------------+---------------+
I'm using the following code but it doesn't return anything
SELECT u.name AS n
,p.post AS t
FROM community AS c
LEFT JOIN users AS u ON u.id_user = c.id_followed
LEFT JOIN posts AS p ON c.id_followed = p.id_user_post
WHERE u.id_follower = 1
Users.id_follower does not exist, so that is why you get nothing, in fact you are likely getting an error (Invalid column name 'id_follower'.).
Use Community.id_follower instead. I would also recommend using more descriptive column names (like 'username' and 'comment')
For the pure reason of answering your specific question, I have used 'n' and 't' in the query.
SELECT u.name as n, p.post as t
FROM Community c
LEFT JOIN Users u ON c.id_followed = u.id_user
LEFT JOIN Posts p ON c.id_followed = p.id_user_post
WHERE c.id_follower = 1
Test:
;WITH USERS AS(
SELECT *
FROM (VALUES (1,'A'),
(2,'B'),
(3,'C'),
(4,'D'),
(5,'E')) U(id_user, name))
, Community AS(
SELECT *
FROM (VALUES (1,2),
(1,3),
(1,5)) C(id_follower, id_followed))
, posts AS(
SELECT *
FROM (VALUES (1,2,'hi'),
(2,3,'hello'),
(3,5,'hey you'),
(4,4,'come on'),
(5,5,'where are you')) P(id_post, id_user_post, post))
SELECT u.name as n, p.post as t
FROM Community c
LEFT JOIN Users u ON c.id_followed = u.id_user
LEFT JOIN Posts p ON c.id_followed = p.id_user_post
WHERE c.id_follower = 1
I've got the following two SQL tables (in MySQL):
Users
| id | name |
|----|------|
| 1 | Luke |
| 2 | Mark |
| 3 | Lucy |
| 4 | Biff |
User category
| user_id | category_id |
|---------|-------------|
| 1 | 5 |
| 1 | 6 |
| 2 | 5 |
| 2 | 7 |
| 3 | 5 |
I want users that are in User category but not if category id is 6.
In this case Mark and Lucy because Luke is in category 6 too and Biff has no category.
There is a way to do it without subquery and only in one query?
You can group by user_id and eliminate those rows where there is atleast one category_id of 6.
select uc.user_id,u.name
from user_category uc
join users u on uc.user_id = u.id
group by uc.user_id,u.name
having sum(case when category_id = 6 then 1 else 0 end) = 0
Join them and check for difference :
SELECT * FROM users
INNER JOIN user_category ON (user_category.user_id = users.id)
WHERE user_category.category_id <> 6
p.s. using group by is not effective, cuz it says to DB engine to do additional group by operation after gathering data.
I have a table which has invite_code and invited_by columns.
I would like to select all users and the number of users that are invited by this user. Could this be done in one query?
e.g. I have these data:
id | invite_code | invited_by
-----------------------------
1 | 11 |
2 | 22 | 11
3 | 33 | 11
4 | 44 | 22
I would like to add a select count(*) from users where u.invite_code = invited_by to the result:
id | invite_code | invited_by | invite_num
------------------------------------------
1 | 11 | | 2
2 | 22 | 11 | 1
3 | 33 | 11 | 0
4 | 44 | 22 | 0
I am using MySQL
try this:
SELECT [id]
,[InvCode]
,[InvBy], (select count(*) from table t where t.[InvBy]=t1.[InvCode])
FROM table t1
You can use a join too instead of the subquery in the column list:
select u1.id, u1.invite_code, u1.invited_by, u2.invite_num
from users u1
left join (select invited_by, count(*) as invite_num from users group by invited_by) u2
on u1.invite_code = u2.invited_by;
The most efficient way to do this is with a self-join:
select u.id, u.invite_code, u.invited_by, count(u2.id) as invite_num
from user u
left outer join user u2 on u2.invited_by = u.invite_code
group by u.id, u.invite_code, u.invited_by