MySQL: Search group from constituent members using AND condition - mysql

Following three SQL tables describe who belongs to which group. For example, Ann belongs to group A/B, Ben belongs to group B/C, and Chris belongs to group A/C.
Now, when I want to search "a group that both Ann AND Ben belong", how should we write the SELECT query? or should we design these tables in different way?
(1) users
+-----------+------------------+
| member_id | member_name |
+-----------+------------------+
| 1 | Ann |
| 2 | Ben |
| 3 | Chris |
+-----------+------------------+
(2) groups
+-----------+------------------+
| group_id | group_name |
+-----------+------------------+
| 1 | Group A |
| 2 | Group B |
| 3 | Group C |
+-----------+------------------+
(3) users_groups
+-----------+------------------+
| group_id | user_id |
+-----------+------------------+
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
| 3 | 2 |
| 1 | 3 |
| 3 | 3 |
+-----------+------------------+

SELECT group_id, group_name
FROM groups
JOIN users_groups ON groups.group_id = users_groups.group_id
JOIN users ON users_groups.user_id = users.member_id
WHERE member_id = 1
INTERSECT
SELECT group_id, group_name
FROM groups
JOIN users_groups ON groups.group_id = users_groups.group_id
JOIN users ON users_groups.user_id = users.member_id
WHERE member_id = 2
This will give the group id and name of all groups where both Ann and Ben are in. Feel free to replace the member_id = # clause with member_name = 'name' if you want to search by their name instead of their id.
EDIT: I forgot that MySQL doesn't have INTERSECT. So the above will work for most DBMSs, but the below should return the desired results for MySQL.
SELECT groups.group_id, groups.group_name
FROM groups
JOIN users_groups ON groups.group_id = users_groups.group_id
JOIN users ON users_groups.user_id = users.member_id
WHERE member_id = 1
AND groups.group_id IN (SELECT groups.group_id
FROM groups
JOIN users_groups ON groups.group_id = users_groups.group_id
JOIN users ON users_groups.user_id = users.member_id
WHERE member_id = 2)

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;

Count distinct records from child table for each user in MYSQL

I have a competition which counts how many species each user has collected.
this is managed by 3 tables:
a parent table called "sub" with collection,each collection is unique, has an id and is associated to a user id.
+----+---------+
| id | user_id |
+----+---------+
| 1 | 1 |
| 2 | 10 |
| 3 | 1 |
| 4 | 3 |
| 5 | 1 |
| 6 | 10 |
+----+---------+
the child table called "sub_items" contains multiple unique records of the specs and is related to the parent table by the sub id to id.(each sub can have multiple records of specs)
+----+--------+---------+--+
| id | sub_id | spec_id | |
+----+--------+---------+--+
| 1 | 1 | 1000 | |
| 2 | 1 | 1003 | |
| 3 | 1 | 2520 | |
| 4 | 2 | 7600 | |
| 5 | 2 | 1000 | |
| 6 | 3 | 15 | |
+----+--------+---------+--+
a user table with associated user_id
+--------+-------+--+
| usename | name |
+---------+-------+--+
| 1 | David |
| 10 | Ruth |
| 3 | Rick |
+--------+-------+--+
i need to list the users with the most unique specs collected in a decsending order.
output expected:
David has a total of 2 unique specs.Ruth has a total of 2 unique specs.
+--------+---------+
| id | total |
+----+-------------+
| David | 2 |
| Ruth | 2 |
| Rick | 2 |
+----+-------------+
so far i have this,it produces a result. but its not accurate, it counts the total records.
im probably missing a DISTINCT somewhere in the sub-query.
SELECT s.id, s.user_id,u.name, sum(t.count) as total
FROM sub s
LEFT JOIN (
SELECT id, sub_id, count(id) as count FROM sub_items GROUP BY sub_id
) t ON t.sub_id = s.id
LEFT JOIN user u ON u.username = s.user_id
GROUP BY user_id
ORDER BY total DESC
i have looked at this solution, but it doesn't consider the unique aspect
You'll first have to get the max "score" for all the users like:
SELECT count(DISTINCT si.id) as total
FROM sub INNER JOIN sub_items si ON sub.id = su.sub_id
GROUP BY sub.user_id
ORDER BY total DESC
LIMIT 1
Then you can use that to restrict your query to users that share that max score:
SELECT u.name, count(DISTINCT si.id) as total
FROM
user u
INNER JOIN sub ON u.usename = sub.user_id
INNER JOIN sub_items si ON sub.id = su.sub_id
GROUP BY u.name
HAVING total =
(
SELECT count(DISTINCT si.id) as total
FROM sub INNER JOIN sub_items si ON sub.id = su.sub_id
GROUP BY sub.user_id
ORDER BY total DESC
LIMIT 1
)
this worked for me, i have to add the
COUNT(distinct spec_id)
to the sub-query
SELECT s.id, s.user_id,u.name, sum(t.count) as total
FROM sub s
LEFT JOIN (
SELECT sub_id, COUNT(distinct spec_id) as count FROM sub_items group by sub_id
) t ON t.sub_id = s.id
LEFT JOIN user u ON u.username = s.user_id
GROUP BY user_id
ORDER BY total DESC

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)

MYSQL JOIN two tables limit results from second table by date

I am trying to retrieve date from two tables using a MYSQL query. I want to join them together were categories.cat_id=topics.topic_cat. Multiple entries may have the same topic_cat, so I only want to SELECT the most recent, which is equal to MAX(topic_date).
The following query shows the correct information from topics, with only one result per topic_cat and that result having the most recent date.
SELECT topic_subject, topic_cat, topic_date
FROM topics
GROUP BY topic_cat DESC
Multiple rows may have the same value for topic_cat, but I only want to retrieve and join only the most recent, MAX(topic_date) and then join to a query which shows the following information from the categories table.
SELECT categories.cat_id, categories.cat_name, categories.cat_description, topics.topic_subject, topics.topic_cat, topics.topic_date, topics.topic_by
FROM categories
LEFT JOIN topics
ON categories.cat_id=topics.topic_cat
GROUP BY cat_id;
This query displays the correct information, except one thing. It shows the topic_cat with the oldest entry, or MIN(topic_date). I have tried the following to get the topic_cat by newest entry or MAX(topic_date), but without success.
SELECT categories.cat_id, categories.cat_name, categories.cat_description
FROM categories
LEFT JOIN (SELECT topic_subject, topic_cat, topic_date, topic_by
FROM topics
GROUP BY topic_cat DESC) AS topics
ON categories.cat_id=topics.topic_cat
Any help or suggestions would be greatly appreciated.
Ok, so here is the sample data and associated desired result.
Table 1 = categories
_______________________________________________________
| cat_id | cat_name | cat_description |
-------------------------------------------------------
| 1 | james | Some information about james|
-------------------------------------------------------
| 2 | myo | Some information about myo |
-------------------------------------------------------
| 3 | brandon | Some information about brandon |
-------------------------------------------------------
Table 2 = topics
__________________________________________________
| topic_subject | topic_cat | topic_date | topic_by |
----------------------------------------------------------
| marcos | 2 | 2013-9-28 | User 1 |
---------------------------------------------------------
| ferdinand | 2 | 2013-9-29 | User 2 |
---------------------------------------------------------
| maria luisa | 2 | 2013-9-30 | User 1 |
---------------------------------------------------------
| Isabella | 1 | 2013-8-24 | User 3 |
--------------------------------------------------------
| Carlos | 3 | 2012-6-21 | User 2 |
--------------------------------------------------------
| Enrique | 3 | 2011-4-2 | User 3 |
---------------------------------------------------------
I would like the query to return the following data based on the above tables:
_________________________________________________________________________________________________
| cat_id | cat_name | cat_description | topic_subject | topic_cat | topic_date | topic_by |
----------------------------------------------------------------------------------------------------------------
| 1 | james | Some information about james | Isabella | 1 | 2013-8-24 | User 3 |
----------------------------------------------------------------------------------------------------------------
| 2 | myo | Some information about myo | maria luisa | 2 | 2013-9-30 | User 1 |
----------------------------------------------------------------------------------------------------------------
| 3 | brandon | Some information about brandon | Carlos | 3 | 2012-6-21 | User 2 |
----------------------------------------------------------------------------------------------------------------
I hope that clarifies things.
Try This:
###
SELECT * FROM categories c
LEFT JOIN topics t ON c.cat_id = t.topic_cat
WHERE c.cat_id IN (SELECT t1.cat_id FROM (
SELECT c.cat_id, c.cat_name, MAX(t.topic_date) AS maxdate FROM categories c
LEFT JOIN topics t ON c.cat_id = t.topic_cat
GROUP BY c.cat_name
) as t1 WHERE t1.maxdate = t.topic_date OR t.topic_date IS NULL );
### without nulls
SELECT * FROM categories c
LEFT JOIN topics t ON c.cat_id = t.topic_cat
WHERE c.cat_id IN (SELECT t1.cat_id FROM (
SELECT c.cat_id, c.cat_name, MAX(t.topic_date) AS maxdate FROM categories c
LEFT JOIN topics t ON c.cat_id = t.topic_cat
GROUP BY c.cat_name
) as t1 WHERE t1.maxdate = t.topic_date);
Try changing
LEFT JOIN (SELECT topic_subject, topic_cat, topic_date, topic_by
FROM topics
GROUP BY topic_cat DESC) AS topics
to:
LEFT JOIN (SELECT topic_subject, topic_cat, topic_date, topic_by
FROM topics
GROUP BY topic_cat
ORDER BY topic_date DESC
LIMIT 0,1) AS topics

How to build a query involving several different tables with different relationships

I'm currently trying to write a query which involves 5 main tables, 2 of which are referring to a 3rd with foreign keys, but not relating to each-other... and one of the first 2 tables is the main subject of the query. Here's a basic synopsis.
instance user
-------- ----
id id
name name
user_id
def def_map
--- ------
id id
name instance_id
user_id def_id
def_data
--------
id
name
def_id
user_id
What I want to do is get a list of all of the 'def_map's for a single user. In each row I'd like the associated def_data to be displayed as well. So the rows would be like:
instance.id, def.id, def.name, def_data.name, user.id
I can figure out how to get all info except def_data.name in the result, or all info except for instance.id ... but can't figure out how to get then all together using one query. Is it possible? I think part of the problem is I don't know if there is a special word that describes this type of query so I would know what to read up on.
I'm using DQL, but examples in SQL would be just as useful. Thanks in advance for any help.
If you can pull the data individually using 2 queries you simply need to UNION them together
SELECT user.id, i.id, d.id, dd.name
FROM user u
INNER JOIN instance i ON u.id=i.user_id
INNER JOIN def d ON dm.user_id = u.id
INNER JOIN def_data dd ON dd.def_id = d.id
UNION ALL
SELECT u.id, i.id AS instance_id, d.id, dd.name
FROM instance i
INNER JOIN user u ON u.id=i.user_id
INNER JOIN defmap dm ON dm.instance_id=i.id
INNER JOIN def_data dd ON dd.def_id=dm.def_id
select I.id, D.id, D.name, DD.name, U.id
from user U inner join instance I on I.user_id = U.id
Inner join def D on D.user_id = U.id
inner join def_map DM on DM.def_id = D.id AND I.id = DM.instance_id
inner join def_data DD on DD.def_id = D.id AND U.id = DD.user_id
Test data:
USER
+----+-------------------------+
| id | name |
+----+-------------------------+
| 1 | Name1 |
+----+-------------------------+
Instance
+----+------+---------+
| id | name | user_id |
+----+------+---------+
| 1 | I1 | 1 |
+----+------+---------+
def_map
+--------+-------------+--------+
| id | instance_id | def_id |
+--------+-------------+--------+
| 1 | 1 | 1 |
+--------+-------------+--------+
def
+--------------+------+
| id | name | user_id |
+--------------+------+
| 1 | df1 | 1 |
+--------------+------+
def_data
+--------+------+--------+---------+
| id | name | def_id | user_id |
+--------+------+--------+---------+
| 1 | dd1 | 1 | 1 |
+--------+------+--------+---------+
Result
+-------------+--------+----------+---------------+---------+
| instance.id | def.id | def.name | def_data.name | user.id |
+-------------+--------+----------+---------------+---------+
| 1 | 1 | df1 | dd1 | 1 |
+-------------+--------+----------+---------------+---------+