Match results in multiple tables MYSQL - mysql

Hi I have a query that is giving me a few problems and it was suggested I ask a separate question about the end result rather than the problem.
So I have three tables and some user input.
the tables are:
users,
usersLanguages and
usersSkills
each table has a related ID the users table has ID and on the other two they have userID to match skills and languages to users
the user input is dynamic but for example it can be 1 for usersLanguages and 2 for usersSkills
The user input is taken from a form and what i need to do is match get the results of users
depending on the language IDs or skill ids passed through. for example i can pass two user ids and three language ID's.
SELECT DISTINCT users.ID, users.name
FROM users
INNER JOIN usersSkills
ON users.ID = usersSkills.userID
INNER JOIN usersLanguages ON users.ID = usersLanguages.userID
WHERE activated = "1"
AND type = "GRADUATE" AND usersSkills.skillID IN(2)
AND usersLanguages.languageID IN(2)
GROUP BY usersSkills.userID HAVING COUNT(*) = 1,
usersLanguages.userID HAVING COUNT(*) = 1

You do not mix group by and having clauses.
having is not a part of group by, in fact you can have having without a group by, in which case it will work as a more capable (and slower) where clause.
SELECT u.ID, u.name
FROM users u
INNER JOIN usersSkills us ON u.ID = us.userID
INNER JOIN al ON u.ID = ul.userID
WHERE u.activated = '1'
AND u.type LIKE 'GRADUATE' AND us.skillID IN('2')
AND ul.languageID IN('2')
GROUP BY u.ID
HAVING COUNT(*) = 1
because of the join criteria, ul.userID = us.userID = u.ID so it makes no sense to group by both. Just group by u.id, because that's the ID you select after all.
u.name is functionally dependent on ID, so that does not need to be listed (*).
When doing a test like u.type = 'GRADUATE', I prefer to do u.type LIKE 'GRADUATE' because LIKE is case insensitive and = may not be depending on the collation, but that's just me.
Distinct is not needed; group by already makes the results distinct.
Having works on the resultset up to and including group by, so in this case you need only one having clause. If you want to have more, you need to treat them the same as a where clause:
having count(*) = 1 AND SUM(money) > 10000
(*) this is only true in MySQL, in other SQL's you need to list all non-aggregated selected columns in your group by clause. Whether they are functionally dependent on the group by column or not. This slows things down and makes for very long query-syntax, but it does prevent a few beginners errors.
Personally I like the MySQL way of doing things.

something like
SELECT * from users left join usersLanguage on users.id=usersLanguage.userID
left join usersSkills on usersSkills.userID=users.id
where usersLanguage.id in (1, 2, 3) and usersSkills.id in (1, 2, 3)
GROUP BY users.id
will probably work

try this
SELECT users.ID,user.name
FROM users
INNER JOIN usersSkills ON users.ID = usersSkills.userId
INNER JOIN AND usersLanguages ON users.ID = usersLanguages.userID
WHERE activate = '1'
AND type = 'GRADUATE'
AND usersSkill.skillID IN (2)
AND usersLanguages.languageID IN (2)
GROUP BY users.ID

Related

How to count rows on joined table without WHERE [duplicate]

This question already has answers here:
Left Outer Join doesn't return all rows from my left table?
(3 answers)
Closed 3 years ago.
I have two related tables users and networks — social-media accounts of corresponding user. Accounts (networks) could be like these: facebook, linkedin, youtube, vimeo, etc...
Every user could have zero or more social-media accounts (in networks table).
In my case I need to count only video accounts (youtube and/or vimeo) of each user.
I'm trying this two queries:
SELECT users.email, COUNT(networks.network) as video_networks_counter,
FROM users
LEFT JOIN networks ON networks.user_id = users.id
WHERE networks.network='youtube' OR networks.network='video'
GROUP BY users.id
and
SELECT users.email,
CASE networks.network WHEN 'youtube' THEN 1 ELSE 0 END as video_networks_counter,
CASE networks.network WHEN 'vimeo' THEN 1 ELSE 0 END as video_networks_counter
FROM users
LEFT JOIN networks ON networks.user_id = users.id
GROUP BY users.id
Which don't work as desired.
Problem with first query is that user could not have any networks but he also have to be in a list with 0 counter. So WHERE condition seems to be useless in my case.
Second query with CASE also doesn't work.
Move the conditions to the on clause:
SELECT u.email, COUNT(n.network) as video_networks_counter
FROM users u LEFT JOIN
networks n
ON n.user_id = u.id AND
n.network IN ('youtube', 'video')
GROUP BY u.email;
Notes:
Your WHERE clause turns the outer join into an inner join, because NULL values do not match.
IN is simpler than OR clauses for this type of comparison.
The GROUP BY key should match the unaggregated columns in the SELECT.
Table aliases make the query easier to write and to read.
You may intend 'vimeo' rather than 'video'.
And, if you want to count them separately:
SELECT u.email, COUNT(n.network) as video_networks_counter,
SUM( n.network = 'youtube') as num_youtube,
SUM( n.network = 'vimeo') as num_vimeo
FROM users u LEFT JOIN
networks n
ON n.user_id = u.id AND
n.network IN ('youtube', 'vimeo')
GROUP BY u.email;
In your 1st query set the conditions in the ON clause and remove WHERE:
SELECT users.email, COUNT(networks.network) as video_networks_counter,
FROM users
LEFT JOIN networks ON networks.user_id = users.id
AND (networks.network='youtube' OR networks.network='video')
GROUP BY users.id
Your WHERE clause removes all the users that do not have an account making the LEFT JOIN useless.
You could just sum up how often your condition is true
SELECT u.email,
SUM(n.network IN ('youtube', 'video')) as video_networks_counter,
FROM users u
LEFT JOIN networks n ON n.user_id = u.id
GROUP BY u.email
You can try this option with CASE and SUM-
SELECT users.email,
SUM(
CASE
WHEN networks.network='youtube' OR networks.network='video' THEN 1
ELSE 0
END
) as video_networks_counter,
FROM users
LEFT JOIN networks ON networks.user_id = users.id
GROUP BY users.email

MYSQL count group by with a left join where there is an entry in a third table

I have a table of groups with details of groups that my users can join and a separate table of group members to show which users have joined which groups. The group_members table just has a column for group_id and a column for user_id. If there is a row with group_id = 3 AND user_id = 10 then we know user 10 is a member of group 3.
I have the following mysql statement that works well to return the details of the groups including the number of members each group has depending upon a couple of conditions in the WHERE clause.
SELECT groups.*, COUNT(group_members.group_id) AS member_count
FROM groups LEFT JOIN
group_members
ON groups.group_id = group_members.group_id
WHERE groups.deleted = 0 AND
groups.trainer_id = ' .$trainer_id .'
GROUP BY groups.group_id
ORDER BY groups.group_name'
The problem is though that users can be deleted in and aren't always removed from the group_members table when this happens. This means that users can show up in the count even though they are no longer actually in the system in a third users table.
So I want to only include group members in the count if they are also present in the users table. I thought I could do this with an INNER JOIN between the group members and users tables. Something like this...
SELECT groups.*, COUNT(users.user_id) AS client_count
FROM groups LEFT JOIN
group_members
ON groups.group_id = group_members.group_id INNER JOIN
users
ON group_members.user_id = users.user_id
WHERE groups.deleted = 0 AND groups.trainer_id = ' .$trainer_id .'
GROUP BY groups.group_id
ORDER BY groups.group_name
The trouble with this is that when a group has no members the group is not showing up in the results. I guess because it is not able to join to the users table.
If anyone could explain to me how I can achieve what I am looking to do I'd be very grateful.
A LEFT JOIN to users` should really fix your problem:
SELECT g.*, COUNT(u.user_id) AS client_count
FROM groups g LEFT JOIN
group_members gm
ON g.group_id = gm.group_id LEFT JOIN
users u
ON gm.user_id = u.user_id
WHERE g.deleted = 0 AND g.trainer_id = ' .$trainer_id .'
GROUP BY g.group_id
ORDER BY g.group_name;
If a group is not being returned, then it does not meet the WHERE filtering conditions. All groups meeting those conditions should be returned if LEFT JOIN is used for both joins.
I would also strongly advise you to use parameters rather than munging query strings, when you call queries from an application language.
You can just do a LEFT JOIN instead of INNER on the users table. The COUNT function skips null entries so your last query should work.

MySQL JOIN ON the one of two columns that doesn't match variable

Let's jump right into it: I've got two simple tables set up in my MySQL database, a users table and a matches table. The users table holds, well, users. The matches table is meant to establish many-to-many connections between users and contains just two userID's.
What is want to query is a list of names of all matched users for the user with userID 1 but I can't wrap my head around it. The problem is that the userID (in this case 1) could be in either one field and I don't have a clue in which one.
Just to clarify; I mean something like this (please don't mind the weird pseudo-code):
SELECT users.name
FROM matches
INNER JOIN users
ON userId = (userId1 OR userId2 DEPENDS ON WHERE)
WHERE userId1 = '1'
OR userId2 = '1';
Could you please tell me if this is possible with MySQL and if so, what I should look for/if you would be so kind, give a simple example.
Thanks a lot.
The user of or in a join condition often prevents MySQL from using an index. The use of union or union all makes the query rather cumbersome. You can do what you want with left outer join:
SELECT coalesce(u1.name, u2.name) as name
FROM matches m LEFT JOIN
users u1
ON u.userId = m.userId1 AND m.userId2 = '1' LEFT JOIN
users u2
ON u.userId = m.userId2 AND m.userId1 = '1'
WHERE '1' in (m.userId1, m.userId2);
This should take advantage of indexes on users for looking up the values. If you want distinct names, then add the distinct keyword.
Try:
SELECT DISTINCT u.name
FROM matches m
INNER JOIN users u
ON (u.userId = m.userId1 AND m.userId2 = '1')
OR (u.userId = m.userId2 AND m.userId1 = '1')
Added DISTINCT to avoid duplicate rows.
See this fiddle.
Here's one way to do it that avoids excessive JOIN logic (to make sure SQL can use indexes on users.userId, matches.userId1, matches.userId2)
SELECT u.`name`
FROM
matches AS m
JOIN users AS u
ON m.userId1=u.userId
AND m.userId2='1'
UNION
SELECT u.`name`
FROM
matches AS m
JOIN users AS u
ON m.userId2=u.userId
AND m.userId1='1'
Something like this:
Select UserId1, UserId2
From Matches
Where UserId1 = 1
Union
Select UserId2, UserId1
From Matches
Where UserId2 = 1
Notice the order of the UserIds have been changed in the Select clause. This will give you a single list of matches with you searched user '1' in a single column and all their matches in the other.
This approach will require you then link in your users table as follows:
Select searchmatches.UserId1, searchmatches.UserId2, leftuser.Name, rightuser.name
From (
Select UserId1, UserId2
From Matches
Where UserId1 = 1
Union
Select UserId2, UserId1
From Matches
Where UserId2 = 1
) searchmatches
inner join users leftuser userMatches.UserId1 = leftuser.UserId
inner join users rightuser userMatches.UserId2 = rightuser.UserId
Hope that Helps! If you want you can remove one of the inner joins to the users table as you know who the left user is as you searched on them!

Order of join conditions important?

Consider this query
SELECT users.id,
users.name
FROM users
LEFT JOIN suppliers
ON users.id = suppliers.id
AND users.hall = suppliers.hall
WHERE USER.payment > 300
In this how will the change of order in the conditions of join matter ( i.e. id second and hall first)?
The order of the Join conditions has no effect on the result since they represent a boolean expression which is analysed in whole.

MySQL query optimization: Multiple SELECT IN to LEFT JOIN

I usually go with the join approach but in this case I am a bit confused. I am not even sure that it is possible at all. I wonder if the following query can be converted to a left join query instead of the multiple select in used:
select
users.id, users.first_name, users.last_name, users.description, users.email
from users
where id in (
select assigned.id_user from assigned where id_project in (
select assigned.id_project from assigned where id_user = 1
)
)
or id in (
select projects.id_user from projects where projects.id in (
select assigned.id_project from assigned where id_user = 1
)
)
This query returns the correct result set. However, I guess the repetition of the query that selects assigned.id_project is a waste.
You could start with the project assignments of user 1 a1. Then find all assignments of other people to those projects a2, and the user in the project table p. The users you are looking for are then in either a2 or p. I added distinct to remove users who can be reached in both ways.
select distinct u.*
from assigned a1
left join
assigned a2
on a1.id_project = a2.id_project
left join
project p
on a1.id_project = p.id
join user u
on u.id = a2.id_user
or u.id = p.id_user
where a1.id_user = 1
Since both subqueries have a condition where assigned.id_user = 1, I start with that query. Let's call that assignment(s) the 'leading assignment'.
Then join the rest, using left joins for the 'optional' tables.
Use an inner join on user that matches either users of assignments linked to the leading assignment or users of projects linked to the leading project.
I use distinct, because I assumen you'd want each user once, event if they have an assignment and a project (or multiple projects).
select distinct
u.id, u.first_name, u.last_name, u.description, u.email
from
assigned a
left join assigned ap on ap.id_project = a.id_project
left join projects p on p.id = a.id_project
inner join users u on u.id = ap.id_user or u.id = p.id_user
where
a.id_user = 1
Here's an alternative way to get rid of the repetition:
SELECT
users.id,
users.first_name,
users.last_name,
users.description,
users.email
FROM users
WHERE id IN (
SELECT up.id_user
FROM (
SELECT id_user, id_project FROM assigned
UNION ALL
SELECT id_user, id FROM projects
) up
INNER JOIN assigned a
ON a.id_project = up.id_project
WHERE a.id_user = 1
)
;
That is, the assigned table's pairs of id_user, id_project are UNIONed with those of projects. The resulting set is then joined with the user_id = 1 projects to obtain the list of all users who share the projects with the ID 1 user. And now it only remains to retrieve the details for those users, which in this case is done in the same way as in your query, i.e. using an IN clause.
I'm sorry to say that I don't have MySQL to thoroughly test the performance of this query and so cannot be quite sure if it is in any way better or worse than your original query or than the one suggested both by #GolezTrol and by #Andomar. Generally I tend to agree with #GolezTrol's comment that a query with simple (semi- or whatever-) joins and repetitive parts might turn out more efficient than an equivalent sophisticated query that doesn't have repetitions. In the end, however, it is testing that must reveal the final answer for you.