MySQL - concatenate and joins - mysql

have 3 following tables:
users (id, name)
projects (id, name)
user_to_project (user_id, project_id)
Every user can be assigned to more than one project and this is stored in the user_to_project table. I want to get a user name and all the projects he's assigned to in one field separated with commas. I tried something like this:
SELECT
users.id AS 'ID',
users.name AS 'Name',
(SELECT GROUP_CONCAT (projects.name SEPARATOR ', ')
FROM user_to_project
INNER JOIN projects ON (projects.id = user_to_project.project_id)
INNER JOIN users ON (users.id = user_to_project.user_id)) AS 'Projects'
FROM users
It gets me all assigned projects in every row which is not that I want. How to fix this?

You can do this with a subquery, but you want a correlation clause:
SELECT u.id, u.name,
(SELECT GROUP_CONCAT(p.name SEPARATOR ', ')
FROM user_to_project tup INNER JOIN
projects p
ON p.id = utp.project_id
WHERE u.id = utp.user_id
) as Projects
FROM users u;
Notes:
Use table aliases. They make a query easier to write and to read.
Don't use single quotes for column aliases. Only use single quotes for string and column names (and your column aliases don't require any escape character).
This is different from a version using INNER JOIN, because this will keep all users, even those with no projects.

I didn't see any reason for this correlated query, and you were missing a condition inside to relate it to the outer query. You also needed a group by clause.
This query should give you all the projects for each ID :
SELECT users.id, users.name ,
GROUP_CONCAT (projects.name SEPARATOR ', ')
FROM user_to_project
INNER JOIN projects ON (projects.id = user_to_project.project_id)
INNER JOIN users ON (users.id = user_to_project.user_id)
GROUP BY users.id,users.name
Note: To make your query work, all you need is to drop the users table from the inner query, and keep the condition.

Related

Alias not working in query

I need to display the name of the user and their department who has raised the maximum number of queries. I wrote the following query by joining 5 tables; user, query, profile, degree, and department.
The problem is in the result the alias is not being used for the column names. For both the columns it appears as just name
select user.name 'USER_NAME',department.name 'DEPT_NAME'
from user
inner join query on (query.user_id=user.id)
inner join profile on (user.profile_id=profile.id)
inner join degree on (profile.degree_id=degree.id)
inner join department on (degree.department_id=department.id)
group by user.name
order by count(query.id) desc
limit 1
Use 'as' keyword in order to use alias name in mysql and also remove single quotes.
select user.name as USER_NAME from user;
The following syntax work perfectly for me :
select U.name AS "USER_NAME", D.name AS "DEPT_NAME"
from user U
inner join query Q on (Q.user_id=U.id)
inner join profile P on (U.profile_id=P.id)
inner join degree C on (P.degree_id=C.id)
inner join department D on (C.department_id=D.id)
group by U.name
order by count(Q.id) desc
limit 1;
Sometimes, Mysql prefer you pout an alias on your column when you use some JOIN.
Try it this way;
select thisuser.name 'USER_NAME',department.name 'DEPT_NAME'
from user as thisuser
inner join query on (query.user_id=thisuser.id)
inner join profile on (thisuser.profile_id=profile.id)
inner join degree on (profile.degree_id=degree.id)
inner join department on (degree.department_id=department.id)
group by thisuser.name
order by count(query.id) desc
limit 1
but this is not a good practice when constructing a query;
it looks like this
select thisuser.name,thisdept.name from
(select name,user_id from user) as thisuser
inner join
(select name,user_id from department) as thisdept
on thisuser.user_id = thisdept.user_id
Simply remove the quotes from your query and it will work, but you could use the keyword AS to make it clear that you're using an alias.
I would also use lower case aliases in order to avoid confusion with keywords, and also use uppercase for keywords;
SELECT user.name AS user_name, department.name AS dept_name
...

How to run two LEFT joins from the same two tables, but to different fields within those tables

i have an information table that has the following fields in it;
id, staffMember, lineManager, description
The staffMember and lineManager fields return integer values that correspond to the id of rows within a users table which has the following columns
id, firstname, surname
I can use the following query to return the info in my information table, substituting the staffMember value for a CONCAT of firstname and surname:
SELECT information.id,
CONCAT( users.firstname, ' ', users.surname ) AS staffMember,
information.lineManager,
LEFT(information.description,200) As description
FROM information
LEFT JOIN users
ON ( information.staffMember = users.id )
LIMIT 0 , 30"
But what i want to be able to do, is repeat the process that's working on the value of staffMember on lineManager as well in the same query (which i then pass as a json string) - however, i know i can't have two LEFT joins to the same table but equating different fields.
Any help would be gratefully received.
It sounds like you want this:
SELECT i.id,
CONCAT(u1.firstname, ' ', u1.surname) AS staffMember,
CONCAT(u2.firstname, ' ', u2.surname AS lineManager,
LEFT(i.description,200) As description
FROM information i
LEFT JOIN users u1
ON i.staffMember = u1.id
LEFT JOIN users u2
on i.lineManager = u2.id
LIMIT 0 , 30
You just perform a LEFT JOIN on the users table twice. Once you will join on the staffMember and the other time you will join on lineManager. By providing a different table alias to the table you can distinguish between the tables and the values.
Of if you want to be clearer:
SELECT i.id,
CONCAT(staff.firstname, ' ', staff.surname) AS staffMember,
CONCAT(manager.firstname, ' ', manager.surname AS lineManager,
LEFT(i.description,200) As description
FROM information i
LEFT JOIN users staff
ON i.staffMember = staff.id
LEFT JOIN users manager
on i.lineManager = manager.id
LIMIT 0 , 30
Something like this:
SELECT information.id FROM information
LEFT JOIN users u ON u.id = information.staffMember
LEFT JOIN users ul ON ul.id = information.lineManager
The letters/words after the table are completely made up by you. They are aliases for the table name that you make up on the fly.

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.

sql with GROUP_CONCAT

I'm running this SQL query
$sql = "select images.image, images.comment as feedDescription,
customers.fullName, CONCAT('[', GROUP_CONCAT(DISTINCT likes.uid),']') as likes,
CONCAT('[', GROUP_CONCAT(DISTINCT CONCAT('{\"userid\":\"', comments.fid, '\", \"comment\":\"', comments.comment, '\"}') separator ','),']') as comments
FROM images
LEFT JOIN customers on images.client_id = customers.client_id
LEFT JOIN likes on images.image = likes.image
LEFT JOIN comments on images.image = comments.image
WHERE images.fid=:userID
ORDER BY images.image LIMIT $offset,$limit";
the only problem is that I am getting only the first row ...
I have images table, customers table (taking the name of the customer by the id i got in the images), likes table (people who did "like" on the image) and comments (people who wrote "comments" on the table)
You are using an aggregation function on a query, so MySQL is automatically returning only one row -- the aggregation of all the data.
In other databases, this would produce an error, because you have a mixture of aggregated and non-aggregated columns. This is a (mis)feature of MySQL called "hidden columns".
Add a group by to your query to fix the problem:
group by images.image, images.comment, customers.fullName
Be sure to add this after the WHERE clause and before the ORDER BY.

Mysql group concat on double join

I have a user table from which I want all values, so I have this query:
SELECT tbl_user.* FROM tbl_user
Now I want one additional column in this result which shows all roles this user has, (or nothing if there are no roles for the user). The role information comes from two additional tables.
The first table contains these two values: userid, roleid
The second table contains roleid and role_name.
So the group concat needs to get all role names based on the roleid's in table1.
I have tried several different ways to do this, but I don't succeed. Either I get only one result with several times the same rolename, or no result at all.
Thanks for your help
Michael
Update: added LEFT JOIN for users with no role.
SELECT
tbl_user.*,
GROUP_CONCAT(role_name) AS roles
FROM
tbl_user LEFT JOIN tbl_roles ON tbl_user.userid = tbl_roles.userid
JOIN tbl_rolenames ON tbl_roles.roleid = tbl_rolenames.roleid
GROUP BY tbl_user.userid
Note that MySQL will permit a GROUP BY on fewer columns than appear in the SELECT list in total, but in other RDBMS you would need to explicitly list out the columns in tbl_user and include them in the GROUP BY, or do an additional self join against tbl_user to get the remaining columns from that table.
Something like:
SELECT
urole.userid,
uall.username,
uall.name,
uall.othercols,
urole.roles
FROM
tbl_user uall JOIN (
SELECT
tbl_user.userid,
GROUP_CONCAT(role_name) AS roles
FROM
tbl_user LEFT JOIN tbl_roles ON tbl_user.userid = tbl_roles.roleid
JOIN tbl_rolenames ON tbl_roles.roleid = tbl_rolenames.roleid
GROUP BY tbl_user.userid
) urole ON uall.userid = urole.userid