Constrain SQL results with GROUP_CONCAT and LEFT JOIN - mysql

I need to constrain a user query regarding user roles (user or admin).
In a datatbase there is a table user_scope containing all user ids and the assigned roles (every user has one entry with a 1 for user, and some have a second entry with 2 for admin). I can't change that database architecture right now.
This is my SELECT on a table users so far which joins data from other tables
SELECT
u.id,
GROUP_CONCAT(DISTINCT scopes.scope ORDER BY scopes.id ASC SEPARATOR ' ') as scope
FROM users as u
LEFT JOIN user_scope on user_scope.user_id = u.id
LEFT JOIN scopes on scopes.id = user_scope.scope_id
The table users
+----+--------+
| id | parent |
+----+--------+
| 1 | Alex |
| 2 | Marc |
| 3 | Cath |
+----+--------+
The table user_scope
+---------+----------+
| user_id | scope_id |
+---------+----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
+---------+----------+
The table scopes
+----+--------+
| id | scope |
+----+--------+
| 1 | user |
| 2 | admin |
+----+--------+
This will yield something like this
+----+------------+
| id | scope |
+----+------------+
| 1 | user admin |
| 2 | user |
| 3 | user |
+----+------------+
The problem arises when I want to filter the results based on one particular role. I tried this
SELECT
u.id,
GROUP_CONCAT(DISTINCT scopes.scope ORDER BY scopes.id ASC SEPARATOR ' ') as scope
FROM users as u
LEFT JOIN user_scope on user_scope.user_id = u.id
LEFT JOIN scopes on scopes.id = user_scope.scope_id and scopes.id = 2
Or 1 respectively. However, that will not reduce the number of returned rows but will only NULL the rows with users that have not the scope admin. I also tried to use a CASE but I can't neither use this in a WHERE statement.
How do I reduce the rows returned in this context? Help is REALLY appreciated.

Use a having clause:
SELECT u.id,
GROUP_CONCAT(DISTINCT s.scope ORDER BY s.id ASC SEPARATOR ' ') as scopes
FROM users u LEFT JOIN
user_scope us
ON us.user_id = u.id LEFT JOIN
scopes s
ON s.id = us.scope_id
GROUP BY u.id
HAVING MAX(s.id = 2) > 0;

Related

How to limit and search number of joined rows from table in multiple joins in mysql

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;

Filtering results from a subquery in MySql

I have a many to many user-sport relation and I'm getting a concatenated string of all sport names a user plays using a subquery. My structure looks like:
user
===============
| id | name |
---------------
| 1 | Hugo |
| 2 | Paco |
| 3 | Luis |
---------------
sport
=================
| id | name |
-----------------
| 1 | tennis |
| 2 | football |
| 3 | handball |
-----------------
user_sport
======================
| user_id | sport_id |
----------------------
| 1 | 3 |
| 1 | 1 |
| 2 | 1 |
----------------------
how do I filter the results with users that play any sport from a list, for example getting all users who play tennis or handball.
I'm trying with this query:
SELECT u.id, u.name,
COALESCE (
(SELECT GROUP_CONCAT(s.name SEPARATOR ', ')
FROM sport AS s
LEFT JOIN user_sport AS us ON us.sport_id = s.id
WHERE us.user_id = u.id),'') AS sports
FROM user u
WHERE us.sport_id IN (1,3)
GROUP BY u.id
ORDER BY g.name
but it is not working because the where clause doesn't know the user_sport table. So, I have to create a new JOIN outside the subquery?
You have to join with user_sport twice. Once to filter it to just the sports in the list, and the other time to get all the sports that the selected users play in.
SELECT u.id, u.name, GROUP_CONCAT(s.name SEPARATOR ', ') AS sports
FROM user AS u
JOIN user_sport AS us_filtered ON u.id = us_filtered.user_id
JOIN user_sport AS us_all ON u.id = us_all.user_id # all
JOIN sport AS s ON s.id = us_all.sport_id
WHERE us_filtered.sport_id IN (1, 3)
GROUP BY u.id
ORDER BY u.name

Selecting multiple table values with doubled joins

I have a query which selects values from different tables via JOIN. But I think I have a problem now, because one table needs to join in again with a different name, but somehow it doesn't work.
An example based on a social network strucutre:
Table "users":
+--------+-----------+
| userid | username |
+--------------------|
| 1 | userOne |
| 2 | userTwo |
| 3 | userThree |
+--------+-----------+
Table "posts":
+--------+---------+-------------------------------+
| postid | userid | text |
+--------------------------------------------------|
| 102 | 1 | "Haha i'm User one" |
| 103 | 1 | "And User one is the best" |
| 104 | 3 | "I'm having fun with user two"|
+--------+---------+-------------------------------+
Table "usertags":
+--------+---------------+
| postid | tagged_userid |
+------------------------|
| 104 | 2 |
+--------+---------------+
This is my query:
SELECT posts.postid,
posts.userid,
posts.text,
users.username,
IFNULL(GROUP_CONCAT(DISTINCT usertags.tagged_userid SEPARATOR ','), NULL) as
taggedusers_id,
IFNULL(GROUP_CONCAT(DISTINCT taggedusers.fullname SEPARATOR ','), NULL) as
taggedusers_name,
FROM posts
JOIN users ON posts.userid = users.userid
LEFT JOIN usertags ON posts.postid = usertags.postid
LEFT JOIN users as taggedusers ON usertags.tagged_userid = users.userid
GROUP BY posts.postid
ORDER BY posts.postid DESC
And that's the result i get:
+--------+---------+---------------------------------------------------------------------+
| postid | userid | text | username | taggedusers_id |
+-----------------------------------------------------------------------|----------------|
| 102 | 1 | "Haha i'm User one" | userOne | NULL |
| 103 | 1 | "And User one is the best" | userOne | NULL |
| 104 | 3 | "I'm having fun with user two"| userThree | 2
+--------+---------+-------------------------------+--------------------+----------------+
The problem:
The column 'taggedusers_name' shows up, but it always shows NULL. I want it to show the usernames of the users which are tagged.
Like this, but in the whole, big output
+---------------+-------------------+
| taggeduser_id | taggeduser_name |
+-----------------------------------|
| 2 | userTwo |
| 2,3 | userTwo,userThree |
| NULL | NULL |
+---------------+-------------------+
So, how is this possible? Do I need to make a multiple SELECT statement? I tried that already, but I failed at this too :/
I'd be glad for help!
The issue is that you're referencing users again without the alias. Since users is already being implicitly INNER JOIN (see this question What is the default MySQL JOIN behaviour, INNER or OUTER?) so the taggedusers table will have to meet the conditions that the author is the same as the tagged user ID.
SELECT posts.postid,
posts.userid,
posts.text,
users.username,
IFNULL(GROUP_CONCAT(DISTINCT usertags.tagged_userid SEPARATOR ','), NULL) as
taggedusers_id,
IFNULL(GROUP_CONCAT(DISTINCT taggedusers.fullname SEPARATOR ','), NULL) as
taggedusers_name,
FROM
posts
JOIN
users
ON posts.userid = users.userid
LEFT JOIN
usertags
ON posts.postid = usertags.postid
LEFT JOIN
users as taggedusers
ON usertags.tagged_userid = taggedusers.userid -- this is (I assume) what you meant
-- ON usertags.tagged_userid = users.userid -- this is your problem
GROUP BY
posts.postid
ORDER BY
posts.postid DESC
One way to avoid this is to always alias tables; in this case you could have aliased users as 'author' or something of the like.
The reason you didn't have the same problem with the distinct IDs is that it was on a table that was joined correctly.

MySQL - Left Outer Join not updating original table

I created a toy dataset where I am trying to count the number of posts for each user. I seem to be getting the correct count values but the count column in the users table is not updated with the values.
I'm new to mysql and very confused! Can somebody tell me what I'm doing wrong?
users:
+---------+------+-------+
| user_id | user | pword |
+---------+------+-------+
| 1 | Amy | abcd |
| 2 | Jess | efgh |
| 3 | Lori | ijkl |
+---------+------+-------+
posts:
+---------+-------------+------+
| post_id | post | user |
+---------+-------------+------+
| 1 | hi | Lori |
| 2 | hello | Jess |
| 3 | hello again | Jess |
| 4 | and again | Jess |
+---------+-------------+------+
mysql> ALTER TABLE users ADD COLUMN post_count INT;
mysql> SELECT u.user_id, COUNT(p.user) AS post_count FROM users u LEFT JOIN posts p ON u.user LIKE p.user GROUP BY u.user_id;
+---------+------------+
| user_id | post_count |
+---------+------------+
| 1 | 0 |
| 2 | 3 |
| 3 | 1 |
+---------+------------+
mysql> SELECT * FROM users;
+---------+------+-------+------------+
| user_id | user | pword | post_count |
+---------+------+-------+------------+
| 1 | Amy | abcd | NULL |
| 2 | Jess | efgh | NULL |
| 3 | Lori | ijkl | NULL |
+---------+------+-------+------------+
Thanks!!
Please try the following...
UPDATE users
JOIN ( SELECT u.user_id AS user_id,
COUNT( p.user ) AS post_count
FROM users u
LEFT JOIN posts p ON u.user LIKE p.user
GROUP BY u.user_id ) postCountFinder
ON users.user_id = postCountFinder.user_id
SET users.post_count = postCountFinder.post_count;
This question takes your list of users and post counts obtained from the following...
SELECT u.user_id,
COUNT( p.user ) AS post_count
FROM users u
LEFT JOIN posts p ON u.user LIKE p.user
GROUP BY u.user_id;
... and performs an INNER JOIN with Users on shared value of user_id, creating a dataset with every row from users having the corresponding count tacked on the end.
We then use the SET command to set the empty post_count from users to its corresponding joined count.
If you have any questions or comments, thenplease feel free to post a Comment accordingly.
You need update statement to update the value in the newly added column.Try this:
Update usr
set usr.post_count=tbl.post_count
from users usr
inner join
(select u.user_id,COUNT(p.user)
AS post_count FROM users u
LEFT JOIN posts p ON u.user LIKE p.user GROUP BY u.user_id ) tbl
on tbl.user_id=usr.user_id

MySQL GroupBy with null/zero results

I'm currently writing a ticket system that has three tables
one for users:
users
+----+-----------+----------+
| ID | FirstName | LastName |
+----+-----------+----------+
| 1 | First | User |
| 2 | Second | User |
| 3 | Third | User |
| 4 | Fourth | User |
| 5 | Fifth | User |
+----+-----------+----------+
one for tickets:
ticket
+----+---------------+
| ID | TicketSubject |
+----+---------------+
| 1 | Ticket #1 |
| 2 | Ticket #2 |
| 3 | Ticket #3 |
| 4 | Ticket #4 |
+----+---------------+
and one to assign users to tickets to action (can be more than one user per ticket):
ticket_assigned
+----+----------+--------+
| ID | TicketID | UserID |
+----+----------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 3 | 5 |
| 5 | 3 | 3 |
+----+----------+--------+
I'm trying to create a summary to show each user, and how many tickets they have assigned to them, example:
+------------+-------+
| Name | Count |
+------------+-------+
| First | 2 |
| Second | 1 |
| Third | 1 |
| Fourth | 0 |
| Fifth | 1 |
| Unassigned | 2 |
+------------+-------+
Note that the last entry is "unassigned", this is the number of records in the ticket table that DONT appear in the ticket_assigned table (thus being, unassigned). Also further note that user "Fourth" is zero, in that that user has no records in the ticket_assigned table.
Here is the current MySQL query I am using:
SELECT
CASE
WHEN users.FirstName IS NULL
THEN 'Unassigned'
ELSE users.FirstName
END as 'UserName',
COUNT(*) as 'TicketCount'
FROM tickets
LEFT OUTER JOIN ticket_assigned ON tickets.ticket_id = ticket_assigned.ticket_id
LEFT OUTER JOIN users ON ticket_assigned.user_id = users.user_id
GROUP BY ticket_assigned.user_id
ORDER BY UserName;
Problem with this is that it's not showing any of the users that don't feature in the ticket_assigned table, I'm essentially getting this:
+------------+-------+
| Name | Count |
+------------+-------+
| First | 2 |
| Second | 1 |
| Third | 1 |
| Fifth | 1 |
| Unassigned | 2 |
+------------+-------+
Is anyone able to assist and tell me how I can modify my query to include users that have no records in the ticket_assigned table? Thanks in advance!
Use a LEFT JOIN with a subquery to aggregate tickets:
SELECT t1.FirstName,
COALESCE(t2.ticket_count, 0) AS num_tickets
FROM users t1
LEFT JOIN
(
SELECT UserID, COUNT(*) AS ticket_count
FROM ticket_assigned
GROUP BY UserID
) t2
ON t1.ID = t2.UserID
UNION ALL
SELECT 'Unassigned', COUNT(*)
FROM tickets t
WHERE NOT EXISTS (SELECT 1 FROM tickets_assigned ta
WHERE ta.ticketId = t.id)
In MySQL, I think you need a left join and union all:
select u.id, u.firstname, count(ta.userId) as num_tickets
from users u left join
tickets_assigned ta
on ta.userId = u.id
group by u.id, u.firstname
union all
select NULL, 'Unassigned', count(*)
from tickets t
where not exists (select 1
from tickets_assigned
where ta.ticketId = t.id
);
I included the u.id in the aggregations. I'm uncomfortable just aggregating (and reporting) by first name, because different people frequently have the same first name, even in a relatively small group.
SELECT
u2.Firstname, IFNULL(tmp.count, 0) AS count
FROM users u2
LEFT JOIN (
SELECT u.id, u.Firstname, COUNT(1) as count
FROM ticket_assigned ta
LEFT JOIN ticket t ON t.id = ta.ticketID
LEFT JOIN users u ON u.id = ta.userID
GROUP BY u.id
) tmp ON tmp.id = u2.id
UNION
SELECT
'Unassigned', count(1) AS count
FROM ticket
WHERE id NOT IN (SELECT ticketid FROM ticket_assigned)