How to write a query to get unique elements from a relation - mysql

I have 2 entities with a many to many relation. Users - Roles_Users - Roles
How can write a query that returns to me all the users that only has exactly one role which is the role name "customer". I wrote something like this:
SELECT `users`.* FROM `users`
INNER JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id`
INNER JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id`
WHERE roles.name not in ('admin' , 'sac', 'superadmin', 'customer_service' , 'supplier');
but it still brought to me users that has more than the role 'customer'.
I need the users that has ONLY the role of 'customer' and nothing else

This is how I would solve it:
SELECT `users`.*
FROM `users`
WHERE id IN
(
SELECT `roles_users`.`user_id`
FROM `roles_users`
INNER JOIN `roles`
ON `roles`.`id` = `roles_users`.`role_id`
GROUP BY `roles_users`.`user_id`
HAVING COUNT(*) = 1 -- only a single role
AND MAX(roles.name) = 'customer' -- and this role is 'customer'
)
Btw, there's no need to use all those backticks:
SELECT *
FROM users
WHERE id IN
(
SELECT roles_users.user_id
FROM roles_users
JOIN roles
ON roles.id = roles_users.role_id
GROUP BY roles_users.user_id
HAVING COUNT(*) = 1 -- only a single role
AND MAX(roles.name) = 'customer' -- and this role is 'customer'
)
Isn't this easier to read (and write)?

Compare role name directly,because using in can get you other non required values
SELECT `users`.* FROM `users` INNER JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id` INNER JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id` WHERE roles.name ='customer'

Why not just look users with the specific role?
SELECT `users`.* FROM `users`
JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id`
JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id`
WHERE `roles`.name = 'customer';

Use a NOT EXISTS subquery in the WHERE clause:
SELECT u.*
FROM users u
INNER JOIN roles_users ru ON ru.user_id = u.id
INNER JOIN roles r ON r.id = ru.role_id
WHERE r.name = 'customer'
AND NOT EXISTS (
SELECT *
FROM roles_users ru1
WHERE ru1.user_id = ru.user_id
AND ru1.role_id <> ru.role_id
)
The NOT EXISTS condition will ensure that no other roles are assigned to the user.
A shorter but slower solution could be:
SELECT u.*
FROM users u
INNER JOIN roles_users ru ON ru.user_id = u.id
INNER JOIN roles r ON r.id = ru.role_id
GROUP BY u.id
HAVING GROUP_CONCAT(r.name) = 'customer'

Related

MySQL chain inner JOIN error

When I log a user, I would like to retrieve his personal information such as his classes and teams. I managed to retrieve the classes, but when I try to retrieve the team it doesn't work.
I have 3 tables (users, teams and classes) and 2 join tables (teams_users and classes_users).
Relation below:
classes (id_classe) <--> classes_users (classe_id, user_id ) <--> users (id_user)
teams (id_team) <--> teams_users (team_id, user_id) <--> users (id_user)
This worked
SELECT * FROM classes `c`
INNER JOIN (SELECT * FROM classes_users `cu`
INNER JOIN (SELECT * FROM users WHERE (users.email = 'tata#gmail.com'))
`u` ON (u.id_user = cu.user_id))
`ui` ON (ui.classe_id = c.id_classe)
But when I tried to add another join below it didn't work.
SELECT * FROM teams_users `tu`
INNER JOIN (SELECT * FROM classes `c`
INNER JOIN (SELECT * FROM classes_users `cu`
INNER JOIN (SELECT * FROM users WHERE (users.email = 'tata#gmail.com'))
`u` ON (u.id_user = cu.user_id))
`ui`ON (ui.classe_id = c.id_classe))
`wc` ON (wc.id_user = tu.user_id)
Why all the parentheses and subqueries? I think this is what you are trying to do:
SELECT *
FROM teams_users tu INNER JOIN
users u
ON u.id_user = tu.user_id INNER JOIN
class_users cu
ON u.id_user = cu.user_id INNER JOIN
classes c
ON cu.classe_id = c.id_classe
WHERE u.email = 'tata#gmail.com';

Select last record mysql on left join

I have a table that stores all users connections ( date & ip ) and i want to retrieve with a single query all the users data (nickname , avatar ...) + the last record of my connections history table of this user ...
SELECT
*
FROM
`users`
LEFT JOIN
`connections_history` ON `users`.`id` = `connections_history`.`guid`
How i can proceed thx
Assuming that connections_history table has an AUTO_INCREMENT column id:
SELECT *
FROM (
SELECT u.*, MAX(h.id) as hid
FROM users u
LEFT JOIN connections_history h ON u.id = h.guid
GROUP BY u.id
) u
LEFT JOIN connections_history h ON h.id = u.hid
Unfortunately Mysql does not support window functions, you need Correlated sub-query to do this.
Try something like this
SELECT *
FROM users
LEFT JOIN connections_history ch
ON users.id = ch.guid
AND EXISTS (SELECT 1
FROM connections_history ch1
WHERE ch.guid = ch1.guid
HAVING Max(ch1.date) = ch.date)
One way is finding the rows with max date for each guid in subquery and then join with users table.
Like this:
select *
from `users` u
left join (
select *
from `connections_history` c
where date = (
select max(date)
from `connections_history` c2
where c.`guid` = c2.`guid`
)
) t on u.`id` = t.`guid`;
You can do this with a correlated subquery in the ON clause:
SELECT u.*, ch.*
FROM `users` u LEFT JOIN
`connections_history` ch
ON ch.`guid` = u.`id` AND
ch.date = (SELECT MAX(ch2.date)
FROM connections_history ch2
WHERE ch.guid = ch2.guid
);
This formulation allows the query to take advantage of an index on connections_history(guid, date).

Select Command Where a field must equal a value but another field can have 2 values

I have the following SQL query:
SELECT users.user_id,
users.first_name,
users.last_name,
roles.role,
roles.role_id,
users.username,
users.description,
users_vs_teams.team_id,
teams.team_name,
teams.status,
teams.notes
FROM teams
INNER JOIN users_vs_teams ON teams.team_id = users_vs_teams.team_id
RIGHT OUTER JOIN users ON users_vs_teams.user_id = users.user_id
INNER JOIN roles ON users.role_id = roles.role_id
WHERE( users.role_id = 3 ) AND ( teams.status = 'Completed' ) OR ( teams.status IS NULL )
I want to display only users with a role_id of 3 but team.status can be either Completed or NULL. However, this query displays all roles where teams.status is either Completed or NULL. Any help resolving this issue will be greatly appreciated.
First, I'm not sure if you need an outer join for this. Second, your problem seems to be parentheses in the WHERE clause:
SELECT u.user_id, u.first_name, u.last_name, r.role, r.role_id,
u.username, u.description, uvt.team_id,
t.team_name, t.status, t.notes
FROM teams t INNER JOIN
users_vs_teams uvt
ON t.team_id = uvt.team_id INNER JOIN
users u
ON uvt.user_id = u.user_id
roles r
ON u.role_id = r.role_id ON u
WHERE (u.role_id = 3) AND (t.status = 'Completed' OR t.status IS NULL)
Note that table aliases make the query easier to write and to read.
Remove the RIGHT OUTER JOIN and fix your parenthesis in your WHERE clause.
SELECT users.user_id,
users.first_name,
users.last_name,
roles.role,
roles.role_id,
users.username,
users.description,
users_vs_teams.team_id,
teams.team_name,
teams.status,
teams.notes
FROM teams
INNER JOIN users_vs_teams ON teams.team_id = users_vs_teams.team_id
INNER JOIN users ON users_vs_teams.user_id = users.user_id
INNER JOIN roles ON users.role_id = roles.role_id
WHERE( users.role_id = 3 ) AND ( teams.status = 'Completed' OR teams.status IS NULL)
you can also do something like this:
( teams.status = 'Completed' OR ISNULL(teams.status,'') = '')

MySQL select from database where not in

I need to clean records from one database according to that if they not exists in other.
It's fairly hard to explain it so here is example:
Table Users
-----------
id
username
password
Table Articles
--------------
id
title
created_by
edited_by
created_by and deleted_by contain the user ID.
I have 3-4 tables with almost the same structure like the articles table and I want to delete users from table users who don't have any record in articles-like tables.
I mean users whos ID cannot be found in any of the articles-like tables in the created_by and edited_by table.
How to do that?
I first tried to see if I can select all the data by all the tables according to users, but the server cannot execute the query:
SELECT * FROM `users`
JOIN `articles`
ON `articles`.`created_by` = `users`.`id`
AND `articles`.`edited_by` = `users`.`id`
JOIN `articles_two`
ON `articles_two`.`created_by` = `users`.`id`
AND `articles_two`.`edited_by` = `users`.`id`
JOIN `articles_three`
ON `articles_three`.`created_by` = `users`.`id`
AND `articles_three`.`edited_by` = `users`.`id`
JOIN `articles_four`
ON `articles_four`.`created_by` = `users`.`id`
AND `articles_four`.`edited_by` = `users`.`id`
JOIN `articles_five`
ON `articles_five`.`created_by` = `users`.`id`
AND `articles_five`.`edited_by` = `users`.`id`
JOIN `articles_six`
ON `articles_six`.`created_by` = `users`.`id`
AND `articles_six`.`edited_by` = `users`.`id`;
I think the cleanest way is not in in the select clause:
select *
from users u
where u.id not in (select created_by from articles where created_by is not null) and
u.id not in (select edited_by from articles where edited_by is not null) and
u.id not in (select created_by from articles_two where created_by is not null) and
u.id not in (select edited_by from articles_two where edited_by is not null) and
u.id not in (select created_by from articles_three where created_by is not null) and
u.id not in (select edited_by from articles_three where edited_by is not null) and
u.id not in (select created_by from articles_four where created_by is not null) and
u.id not in (select edited_by from articles_four where edited_by is not null)
Performance should be helped by having indexes on the various created_by and edited_by columns.
This should work. It's not terribly elegant but I think it's easy to follow:
DELETE FROM Users
WHERE ID NOT IN (
SELECT Created_By FROM Articles
UNION SELECT Edited_By FROM Articles
UNION SELECT Created_By FROM Articles_Two
UNION SELECT Edited_By FROM Articles_Two
...
UNION SELECT Created_By FROM Articles_Six
UNION SELECT Edited_By FROM Articles_Six
)
As with any big "cleanup" query, (a) make a copy of the table first and (b) review carefully before typing COMMIT.
In MySQL, left outer join ... where null tends to perform better than in or exists (where there are appropriate indexes), so the following should be worth trying:
SELECT u.* FROM `users` u
LEFT JOIN `articles` ac1 ON ac1.`created_by` = u.`id`
LEFT JOIN `articles_two` ac2 ON ac2.`created_by` = u.`id`
LEFT JOIN `articles_three` ac3 ON ac3.`created_by` = u.`id`
LEFT JOIN `articles_four` ac4 ON ac4.`created_by` = u.`id`
LEFT JOIN `articles_five` ac5 ON ac5.`created_by` = u.`id`
LEFT JOIN `articles_six` ac6 ON ac6.`created_by` = u.`id`
LEFT JOIN `articles` ae1 ON ae1.`edited_by` = u.`id`
LEFT JOIN `articles_two` ae2 ON ae2.`edited_by` = u.`id`
LEFT JOIN `articles_three` ae3 ON ae3.`edited_by` = u.`id`
LEFT JOIN `articles_four` ae4 ON ae4.`edited_by` = u.`id`
LEFT JOIN `articles_five` ae5 ON ae5.`edited_by` = u.`id`
LEFT JOIN `articles_six` ae6 ON ae6.`edited_by` = u.`id`
WHERE COALESCE(ac1.`created_by`,ac2.`created_by`,ac3.`created_by`,
ac4.`created_by`,ac5.`created_by`,ac6.`created_by`,
ae1.`edited_by`, ae2.`edited_by`, ae3.`edited_by`,
ae4.`edited_by`, ae5.`edited_by`, ae6.`edited_by`)
IS NULL;

MySQL: delete from IN

I know this is a simple syntax issue. Trying to delete all users from a subquery:
delete from users
where id IN (
select u.id
from users u
where not exists (select * from stickies i where i.user_id = u.id)
group by u.email
having count(*) > 1
)
Getting this error:
error : You can't specify target table 'users' for update in FROM clause
The subquery works fine (returns list of user id's).
DELETE u.*
FROM users u JOIN (
SELECT u.id
FROM users u LEFT JOIN stickies i ON i.user_id = u.id
WHERE i.user_id IS NULL
GROUP BY u.email
HAVING COUNT(*) > 1
) r ON r.id = r.id
Note: in the inner query, you are grouping by email, but selecting a user ID. this may return non deterministic results.