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)
I have a table like this
| user_id | company_id | employee_id |
|---------|------------|-------------|
| 1 | 2 | 123 |
| 2 | 2 | 123 |
| 3 | 5 | 432 |
| 4 | 5 | 432 |
| 5 | 7 | 432 |
I have a query that looks like this
SELECT COUNT(*) AS Repeated, employee_id, GROUP_CONCAT(user_id) as user_ids, GROUP_CONCAT(username)
FROM user_company
INNER JOIN user ON user.id = user_company.user_id
WHERE employee_id IS NOT NULL
AND user_company.deleted_at IS NULL
GROUP BY employee_id, company_id
HAVING Repeated >1;
The results I am getting look like this
| Repeated | employee_id | user_ids |
|---------|--------------|------------|
| 2 | 123 | 2,3 |
| 2 | 432 | 7,8 |
I need results that look like this
| user_id |
|---------|
| 2 |
| 3 |
| 7 |
| 8 |
I realize my query is getting more, but that's just to make sure I'm getting the correct data. Now I need to get a single column result with each user_id in a new row for updating based on user_id in another query. I've tried this by only selecting the user_id but I only get two rows, I need all four rows of duplicates.
Any ideas on how to modify my query?
Here is the query to get all of your user_ids:
SELECT user_id
FROM user_company uc
INNER JOIN
(
SELECT employee_id, company_id
FROM user_company
WHERE employee_id IS NOT NULL
AND deleted_at IS NULL
GROUP BY employee_id, company_id
HAVING COUNT(employee_id) >1
) AS `emps`
ON emps.employee_id = uc.`employee_id`
AND emps.company_id = uc.`company_id`;
This query below will generate the query you are looking for.
SELECT CONCAT('UPDATE user_company SET employee_id = null WHERE user_id IN (', GROUP_CONCAT(user_id SEPARATOR ', '),')') AS user_sql
FROM user_company uc
INNER JOIN
(SELECT employee_id, company_id
FROM user_company
WHERE employee_id IS NOT NULL
AND deleted_at IS NULL
GROUP BY employee_id, company_id
HAVING COUNT(employee_id) >1) AS `emps`
ON emps.employee_id = uc.`employee_id`
AND emps.company_id = uc.`company_id`;
I'm struggling with this SQL query. Say I have these two tables
**USERS**
+----+-------+
| id | name |
+----+-------+
| 1 | james |
| 2 | tom |
| 3 | kate |
+----+-------+
**PHOTOS**
+-----------+-----------+---------+
| name | sent_from | sent_to |
+-----------+-----------+---------+
| beach.jpg | 1 | 2 |
| trees.jpg | 3 | 1 |
| earth.jpg | 2 | 1 |
+-----------+-----------+---------+
How could I get, using one SQL query, all the users that have more sent_to associated with their id than sent_from ?
I think of this as aggregating the data twice and then doing the comparison:
select sf.sent_from
from (select sent_from, count(*) as numsent
from photos
group by sent_from
) sf left outer join
(select sent_to, count(*) as numrecv
from photos
group by sent_to
) st
on sf.sent_from, st.sent_to
where numsent > numrecv;
If you want user information, then join that in.
An alternative way restructures the data first and then does the aggregation:
select who
from (select sent_from as who, 1 as sent_from, 0 as sent_to
from photos
union all
select sent_to as who, 0, 1
from photos
) p
group by who
having sum(sent_from) > sum(sent_to);
I think here is something that might help you:
SELECT * FROM (
SELECT `id`, `name`,
IFNULL((SELECT count(*) FROM `photos` WHERE `sent_from` = `users`.`id`),0) AS `sent_from_count`,
IFNULL((SELECT count(*) FROM `photos` WHERE `sent_t`o = `users`.`id`),0) AS `sent_to_count`
FROM `users`) AS `t1`
WHERE `t1`.`sent_to_count` > `t1`.`sent_to_count`
Anyone can explain behind this logic in MySQL IN clause and help me understand this issue
I have a user table and this table users are belongs to one or many groups.
The group table primary key reference is updated in users table by comma(,) separated values as follows
Query 1. SELECT * FROM user;
+---------+-----------+-------------------------+-----------+
| user_id | user_name | user_email | group_id |
+---------+-----------+-------------------------+-----------+
| 1 | suresh | xxxx#yyyyyyyyyy.com | 22 |
| 2 | sundar | s7sundera#gmail.com | 2 |
| 3 | tester | xxxxxxxx#yyyyyyyyyy.com | 1,2,3,4 |
| 4 | gail | zzzzzz#gmail.com | 1,2,3,4,5 |
+---------+-----------+-------------------------+-----------+
If I use IN clause and group id value as 2 in MySQL I got only one result
Query 2. SELECT * FROM user WHERE group_id IN(2)
+---------+-----------+---------------------+----------+
| user_id | user_name | user_email | group_id |
+---------+-----------+---------------------+----------+
| 2 | sundar | s7sundera#gmail.com | 2 |
+---------+-----------+---------------------+----------+
If I use IN clause and group id value as (1,2) in MySQL I got three results
Query 3. SELECT * FROM user WHERE group_id IN(1,2)
+---------+-----------+-------------------------+-----------+
| user_id | user_name | user_email | group_id |
+---------+-----------+-------------------------+-----------+
| 2 | sundar | s7sundera#gmail.com | 2 |
| 3 | tester | xxxxxxxx#yyyyyyyyyy.com | 1,2,3,4 |
| 4 | gail | zzzzzz#gmail.com | 1,2,3,4,5 |
+---------+-----------+-------------------------+-----------+
I want to get group id 2 users like following output but it is not working as expected
If I use this query I need to get query 3 results is it possible?
SELECT * FROM user WHERE group_id IN(2)
This is too long to be a comment, but you need to reconsider your current table design. You should not be storing the group_id values as a comma separated list.
Your tables should be structured similar to the following:
create table user
(
user_id int, PK
user_name varchar(50),
user_email varchar(100)
);
create table groups
(
group_id int, PK
group_name varchar(10)
);
create table user_group
(
user_id int,
group_id int
);
The user_group table will have a Primary Key of both the user_id and the group_id so you cannot get duplicates and then these columns should be foreign keys to the respective tables. This table will allow you to have multiple groups for each user_id.
Then when you query your tables, the query will be:
select u.user_id,
u.user_name,
u.user_email,
g.group_id
from user u
inner join user_group ug
on u.user_id = ug.user_id
inner join groups g
on ug.group_id = g.group_id
See SQL Fiddle with Demo.
If you needed to for display purposes show the group_id values in a comma separated list, you can use GROUP_CONCAT():
select u.user_id,
u.user_name,
u.user_email,
group_concat(g.group_id order by g.group_id) group_id
from user u
inner join user_group ug
on u.user_id = ug.user_id
inner join groups g
on ug.group_id = g.group_id
group by u.user_id, u.user_name, u.user_email
See SQL Fiddle with Demo
If you redesign your tables, then when you search it becomes much easier:
select u.user_id,
u.user_name,
u.user_email,
g.group_id
from user u
inner join user_group ug
on u.user_id = ug.user_id
inner join groups g
on ug.group_id = g.group_id
where g.group_id in (1, 2)
See SQL Fiddle with Demo
When passing 1,2 to the IN operator, you're asking for 1 and 2; this is why it will return all three results. If you have a column with comma separated values, you're violating normal form; as each column should not contain more than one value. If you want to find a single value in a multi-valued comma separated column, then you can use FIND_IN_SET.
A normalized schema would look like:
+---------+-----------+-------------------------+
| user_id | user_name | user_email |
+---------+-----------+-------------------------+
| 2 | sundar | s7sundera#gmail.com |
| 3 | tester | xxxxxxxx#yyyyyyyyyy.com |
| 4 | gail | zzzzzz#gmail.com |
+---------+-----------+-------------------------+
+---------+-----------+
| user_id | group_id |
+---------+-----------+
| 2 | 2 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
| 3 | 4 |
| 4 | 1 |
| 4 | 2 |
| 4 | 3 |
| 4 | 4 |
| 4 | 5 |
+---------+-----------+
+----------+
| group_id |
+----------+
| 1 |
| 2 |
| 3 |
| 4 |
+----------+
MySQL doesn't treat comma separated lists as anything more than just a string. When you do WHERE group_id IN(2), it converts group_id to an INT, so it can compare it with 2.
When casting to an INT, MySQL stops at the first non-number character.
For example, '1,2,3,4,5' IN (2) becomes 1 IN (2). Which is FALSE.
You can try to use FIND_IN_SET to do what you want, but it's not very efficient (because it can't use indexes; it need to read every single row to see if it matches).
WHERE FIND_IN_SET(2, group_id)
To search for multiple rows, use OR.
WHERE FIND_IN_SET(1, group_id) OR FIND_IN_SET(2, group_id)
The correct way to do this, is to create a "link table" that contains one (or more) rows for each user, showing what group(s) they are in.
EXPLANATION
What is the logic of the query SELECT * FROM user WHERE group_id IN(1,2); ?
You gave a list of numbers (1,2)
The groud_id was being compare numerically
Anything that numerically matched 1 or 2 up to the first comma came up as a result
SUGGESTION
What I am about to present to you may seem rather unorthodox but please follow me...
Here is the query that will get every row that has both 1 and 2 in group_ids:
SELECT user.* FROM
(SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
FROM user) U WHERE LOCATE(',2,',group_ids)) U1
INNER JOIN
(SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
FROM user) U WHERE LOCATE(',4,',group_ids)) U2
ON U1.id = U2.id
INNER JOIN user ON user.id = U2.id;
Here is the code create our sample data
DROP DATABASE IF EXISTS sundar;
CREATE DATABASE sundar;
use sundar
CREATE TABLE user
(
id int not null auto_increment,
user_name VARCHAR(30),
user_email VARCHAR(70),
group_id VARCHAR(128),
PRIMARY KEY (id)
);
INSERT INTO user (user_name,user_email,group_id) VALUES
('suresh' , 'xxxx#yyyyyyyyyy.com' ,'22'),
('sundar' , 's7sundera#gmail.com' ,'2'),
('tester' , 'xxxxxxxx#yyyyyyyyyy.com' ,'1,2,3,4'),
('gail' , 'zzzzzz#gmail.com' ,'1,2,3,4,5');
SELECT * FROM user;
Let's create your sample
mysql> DROP DATABASE IF EXISTS sundar;
Query OK, 1 row affected (0.00 sec)
mysql> CREATE DATABASE sundar;
Query OK, 1 row affected (0.00 sec)
mysql> use sundar
Database changed
mysql> CREATE TABLE user
-> (
-> id int not null auto_increment,
-> user_name VARCHAR(30),
-> user_email VARCHAR(70),
-> group_id VARCHAR(128),
-> PRIMARY KEY (id)
-> );
Query OK, 0 rows affected (0.04 sec)
mysql> INSERT INTO user (user_name,user_email,group_id) VALUES
-> ('suresh' , 'xxxx#yyyyyyyyyy.com' ,'22'),
-> ('sundar' , 's7sundera#gmail.com' ,'2'),
-> ('tester' , 'xxxxxxxx#yyyyyyyyyy.com' ,'1,2,3,4'),
-> ('gail' , 'zzzzzz#gmail.com' ,'1,2,3,4,5');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql>
and here is what it looks like
mysql> SELECT * FROM user;
+----+-----------+-------------------------+-----------+
| id | user_name | user_email | group_id |
+----+-----------+-------------------------+-----------+
| 1 | suresh | xxxx#yyyyyyyyyy.com | 22 |
| 2 | sundar | s7sundera#gmail.com | 2 |
| 3 | tester | xxxxxxxx#yyyyyyyyyy.com | 1,2,3,4 |
| 4 | gail | zzzzzz#gmail.com | 1,2,3,4,5 |
+----+-----------+-------------------------+-----------+
4 rows in set (0.00 sec)
mysql>
Again, here is the messy query that will get what you want:
SELECT user.* FROM
(SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
FROM user) U WHERE LOCATE(',1,',group_ids)) U1
INNER JOIN
(SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
FROM user) U WHERE LOCATE(',2,',group_ids)) U2
ON U1.id = U2.id
INNER JOIN user ON user.id = U2.id;
Here it is executed:
mysql> SELECT user.* FROM
-> (SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
-> FROM user) U WHERE LOCATE(',1,',group_ids)) U1
-> INNER JOIN
-> (SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
-> FROM user) U WHERE LOCATE(',2,',group_ids)) U2
-> ON U1.id = U2.id
-> INNER JOIN user ON user.id = U2.id;
+----+-----------+-------------------------+-----------+
| id | user_name | user_email | group_id |
+----+-----------+-------------------------+-----------+
| 3 | tester | xxxxxxxx#yyyyyyyyyy.com | 1,2,3,4 |
| 4 | gail | zzzzzz#gmail.com | 1,2,3,4,5 |
+----+-----------+-------------------------+-----------+
2 rows in set (0.00 sec)
mysql>
OK, how about looking for (2,4) ?
mysql> SELECT user.* FROM
-> (SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
-> FROM user) U WHERE LOCATE(',2,',group_ids)) U1
-> INNER JOIN
-> (SELECT * FROM (SELECT id,CONCAT(',',group_id ,',') group_ids
-> FROM user) U WHERE LOCATE(',4,',group_ids)) U2
-> ON U1.id = U2.id
-> INNER JOIN user ON user.id = U2.id;
+----+-----------+-------------------------+-----------+
| id | user_name | user_email | group_id |
+----+-----------+-------------------------+-----------+
| 3 | tester | xxxxxxxx#yyyyyyyyyy.com | 1,2,3,4 |
| 4 | gail | zzzzzz#gmail.com | 1,2,3,4,5 |
+----+-----------+-------------------------+-----------+
2 rows in set (0.00 sec)
mysql>
Looks like it works.
Give it a Try !!!