How to JOIN three tables with IN array filter, but still get all the rows from third table - mysql

I have Users and Skills table connected through UserSkills table for many-to-many relation. Here is what's in the tables:
Users (id, username)
Skills (id, name)
UserSkills (user_id, skill_id)
I have an array of skills that I am looking for ['C#', 'SQL']. So I am doing join with IN filtering operation to get only those users that I need.
SELECT (username, GROUP_CONCAT(Skills.name)) FROM Users
JOIN UserSkills ON Users.id = UserSkills.user_id
JOIN Skills ON Skills.id = UserSkills.skill_id AND Skills.name IN ('C#', 'SQL')
GROUP BY Users.id
I want to get same users list but with all their skills, not only those that I am looking for. For instance:
In my case I get: username: 'joe' | skills: 'C#', 'SQL'
I want to get: username: 'joe' | skills : 'C#', 'SQL', 'JavaScript', 'NodeJS'
Please help.

The most straight forward way would be to just join a second time:
SELECT u.username, GROUP_CONCAT(DISTINCT userS.name)
FROM Users AS u
INNER JOIN UserSkills AS us ON u.id = us.user_id
INNER JOIN Skills AS filterS ON filterS.id = us.skill_id AND filterS.name IN ('C#', 'SQL')
INNER JOIN Skills AS userS ON userS.id = us.skill_id
GROUP BY u.id
But you will need the DISTINCT in GROUP_CONCAT as the filter skills will basically cross join with the user skills. Also worth noting is this query gets users with any of the skills specified not all. This is a better solution:
SELECT u.username, GROUP_CONCAT(s.name) AS skils
, COUNT(CASE WHEN s.name IN ('C#', 'SQL') THEN 1 ELSE NULL END) AS matchedSkillCount
FROM Users AS u
INNER JOIN UserSkills AS us ON u.id = us.user_id
INNER JOIN Skills AS s ON s.id = us.skill_id
GROUP BY u.id
HAVING matchedSkillCount > 0 -- You can replace 0 with the number of matches you want
;
...but on very large databases, the above might become costly; a couple other alternatives:
SELECT u.username, GROUP_CONCAT(DISTINCT userS.name)
FROM Skills AS filterS
INNER JOIN UserSkills AS ufs ON filterS.id = ufs.skill_id
INNER JOIN Users AS u ON ufs.user_id = u.id
INNER JOIN UserSkills AS us ON u.id = us.user_id
INNER JOIN Skills AS userS ON us.skill_id = userS.id
WHERE filterS.name IN ('C#', 'SQL')
GROUP BY u.id;
SELECT u.username, GROUP_CONCAT(s.name) AS skils
FROM Users AS u
INNER JOIN UserSkills AS us ON u.id = us.user_id
INNER JOIN Skills AS sON s.id = us.skill_id
WHERE u.id IN (
SELECT DISTINCT ufs.user_id
FROM Skills AS filterS
INNER JOIN UserSkills AS ufs ON filterS.id = ufs.skill_id
WHERE filterS.name IN ('C#', 'SQL')
)
GROUP BY u.id
;
Logically, the WHERE is applied after the joins; but most RDBMS (like MySQL) optimize execution by using the WHERE conditions to minimize the rows joined together.

select USERS.name,GROUP_CONCAT(Skills.name) from USERS join USERSKILLS on users.id =
USERSKILLS.user_id join skills on USERSKILLS.SKILL_ID = skills.ID and users.id in (
select distinct users.id from
users join USERSKILLS on users.id = USERSKILLS.user_id join skills on skills.id = userSkills.skill_id and
Skills.name IN ('C#', 'SQL'))
group by USERS.name;

Related

SELECT data from multiple tables using joins in MySQL

i have follwing tables
USERS :
* id
* name
* role id
* phone
STUDENTS
1: userid ( fk users.id)
2: course_id
STAFF
1: staff_id(fk users.id)
2: course-id
COURSES
1: COURSE_ID
1:COURSENAME
I need to find all accounts associated with a number
I need name,userid,roleid,courseid,corsename,staffid, by providing mobile
i have written this query but this return zero results
SELECT users.name, users.id, staff.user_id, students.course_id, users.role_id, courses.course_title FROM users INNER JOIN students ON users.id = students.user_id INNER JOIN staff ON staff.user_id = users.id INNER JOIN courses ON courses.id = students.course_id WHERE users.phone = '9495990028'
LIMIT 0 , 30
Try union operator:
SELECT name, s.userid, roleid, s.course_id, c.coursename, null
FROM USERS u
INNER JOIN STUDENTS s
INNER JOIN COURSES c
ON s.userid = u.id AND c.course_id = s.course_id
WHERE u.phone = ?
UNION
SELECT name, null, roleid, s.course_id, c.coursename, s.staff_id
FROM USERS u
INNER JOIN STAFF s
INNER JOIN COURSES c
ON s.staff_id = u.id AND c.course_id = s.course_id
WHERE u.phone = ?;
For the COURSES table, I check the id with the course_id from students and staff
SELECT name, s.userid, roleid, s.course_id, c.coursename, st.staff_id
FROM USERS u
INNER JOIN STUDENTS s ON s.userid = u.id
INNER JOIN STAFF st ON st.staff_id = u.id
INNER JOIN COURSES c ON c.course_id = s.course_id AND c.course_id = st.course_id
WHERE u.phone = ?

Get only those rows where column a has as many duplicate entries, as many there is distinctive values in column b

My current query:
select users.id as user_id, opportunities.id as op_id, opportunities.title, certificates.id as cert_id from opportunities
join opportunity_certificates on opportunities.id=opportunity_certificates.opportunity_id
join certificates on opportunity_certificates.certificate_id=certificates.id
join user_certificates on certificates.id=user_certificates.certificate_id
join users on user_certificates.user_id=users.id
where opportunity_certificates.is_required = 1 and
opportunities.id = 1
This produces the table on the picture below.
cert_id column can have values from 1 to 7, depends on the opportunities.id. In the table below, I want the query to return only the rows which have the same user_id but different cert_id, 1 and 2.
If the table had 3 different cert_id, I would want it to return only the rows which have same user_id but different cert_id, 1,2 and 3.
when the cert_id has only one value, query should return all the records with that one value in cert_id. Basically, it should show all users who have all required certificates.
The query has to be in the current format. I experimented with
group by users.id
having count(*) >
but I don't know how to make that comparison dynamic, relative to the count of distinctive values in the cert_id column.
Compare counts with a having condition.
select u.id as user_id --, o.id as op_id, o.title
from opportunities o
join opportunity_certificates oc on o.id=oc.opportunity_id
join certificates c on oc.certificate_id=c.id
join user_certificates uc on c.id=uc.certificate_id
join users u on uc.user_id=u.id
where oc.is_required = 1 and o.id = 1
group by u.id --,o.id,o.title
having count(distinct c.id)=(select count(distinct id) from certificates)
Useful?
with data as (
select users.id as user_id, o.title, c.id as cert_id
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
)
select user_id, min(title) as title, max(cert_id) as num_certs
from data
group by user_id
having count(cert_id) = (select max(cert_id) from data);
I'm assuming that cert_id values start and 1 and run sequentially. You could also use count(distinct ...) in the having clause but it guess it's debatable which ones expresses you intent more clearly.
If your version of MySQL doesn't support CTEs then you should be able to just drop that whole subquery into the having clause as well.
select u.id as user_id, min(o.title) as title, max(c.cert_id) as num_certs
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
group by u.id
having count(c.cert_id) = (
select max(c.cert_id)
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
);
Here's another one that might work if you have window functions available. (It might work with Laravel better?):
select *
from (
select users.id as user_id, o.title,
count(distinct c.id) over (partition by u.id) as user_certs,
max(c.id) over () as total_certs
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
) t
where user_certs = total_certs;

Fetching data in case of one -to- many relationship in MySQL

I have 3 tables users, user_group, and groups.
users have one to many relationship with groups.
If I want to fetch only those users who don't have group Mathematics.
I have using the following query for this purpose:
SELECT * FROM users
INNER JOIN user_group ON user_group.user_id = user.UserID
INNER JOIN groups ON user_group.group_id = groups.group_id
WHERE groups.Name <> 'Mathematics';
But it is returning multiple records against all Users. Suppose, if I have user John and he joined 3 groups Science, Mathematics and English. In this case, it will return two records of user John. I want to remove user John totally from the list.
You can use NOT EXISTS:
SELECT *
FROM users AS u
WHERE NOT EXISTS (SELECT 1
FROM user_group AS ug
INNER JOIN groups AS g ON ug.group_id = g.group_id
WHERE ug.user_id = u.UserID AND g.Name = 'Mathematics');
Demo here
If you want to do it using joins, then this is a way:
SELECT u.*
FROM users AS u
LEFT JOIN (
SELECT user_id
FROM user_group
INNER JOIN groups
ON user_group.group_id = groups.group_id AND groups.Name = 'Mathematics'
) AS g ON u.UserID = g.user_id
WHERE g.user_id IS NULL
Demo here
SELECT * FROM users
LEFT JOIN user_group ON user.UserID = user_group.user_id
LEFT JOIN groups ON user_group.group_id = groups.group_id
WHERE groups.Name != 'Mathematics';

Select distinct didn't work correctly (MySQL)

I have a query like that:
SELECT DISTINCT r.name AS role, r.id AS role_id, u.id AS user_id, concat( u.firstname, '', u.lastname ) AS name
FROM users u
INNER JOIN members m ON u.id = m.user_id
INNER JOIN member_roles mr ON m.id = mr.member_id
INNER JOIN roles r ON r.id = mr.role_id
WHERE m.project_id =11
My goal is to get unique u.id(user_id). Unfortunately, the result show two duplicate u.id from this multiple join. If I remove the last one join(INNER JOIN roles r ON r.id = mr.role_id), this query run correctly. But I didn't know why it was wrong if I add this line to my query. The last one join line is necessary to get the role's name, so I can ignore it. Can someone tell me where I did incorrectly in my query?
Use GROUP BY in your query to get rows with unique user_id.
Then the updated query will be look like the following.
SELECT DISTINCT r.name AS role, r.id AS role_id, u.id AS user_id,
concat( u.firstname, '', u.lastname ) AS name
FROM users u
INNER JOIN members m ON u.id = m.user_id
INNER JOIN member_roles mr ON m.id = mr.member_id
INNER JOIN roles r ON r.id = mr.role_id
WHERE m.project_id =11 GROUP BY user_id
You are missing the fact that users can have multiple roles on a project. Fortunately, MySQL has group_concat() which can bring the multiple values together:
SELECT u.id AS user_id, concat( u.firstname, '', u.lastname ) AS name,
GROUP_CONCAT(r.name) as roles,
GROUP_CONCAT(r.id) as role_ids
FROM users u INNER JOIN
members m
ON u.id = m.user_id INNER JOIN
member_roles mr ON m.id = mr.member_id INNER JOIN
roles r
ON r.id = mr.role_id
WHERE m.project_id = 11
GROUP BY u.id;

Select two columns from two different table with multiple join[Mysql]

I have four tables are users(id,name), roles(id,name), members(project_id,user_id,id), and member_roles(member_id, role_id). So how can I select users.name and roles.name from this tables simultaneously(project_id is the condition to select exactly the names what we need)?. I can select users.name and roles.name separately but when I connect two queries, my code was failed. This is the query to get users.name
select users.name from users
inner join members on members.user_id = users.id
where project_id = 1
and here is the query to get roles.name
select roles.name from roles
inner join member_roles on member_roles.role_id = roles.id
join members on members.id = member_roles.member_id
where project_id = 1
You're almost there. Also, to avoid ambiguity, supply an Alias on column Name.
SELECT DISTINCT
u.Name AS member_name,
r.Name AS role_name
FROM users u
INNER JOIN members m
ON u.id = m.user_id
INNER JOIN member_roles mr
ON m.id = mr.member_id
INNER JOIN roles r
ON r.id = mr.role_id
WHERE m.project_id = 1
How do you connect 2 queries? How about something like this:
select u.name as user_name, r.name as role_name from users u join members m on m.user_id = u.id join member_roles mr on mr.member_id = m.id join roles r on r.id = mr.role_id where m.project_id = 1;