I have following tables:
Table: person
id | name
1 | John
2 | Ana
3 | Thomas
Table: fruit
id | name
1 | orange
2 | banana
3 | grapefruit
4 | lemon
5 | apricot
Table: person_fruit
person_id | fruit_id
1 | 1
1 | 3
2 | 1
2 | 2
1 | 5
As you guessed, the person_fruit table serves as the many to many relation.
I'm doing a query for listing all the persons and their favourite fruits concatenated in a string. My problem is that I cannot display their favourite fruits ordered by fruit name like:
John | apricot, grapefruit, orange
Ana | banana, orange
Thomas | NULL
My current MySQL query looks like:
SELECT
p.name,
GROUP_CONCAT(f.name SEPARATOR ', ') fruit
FROM
person p
LEFT JOIN person_fruit pf
ON p.id = pf.person_id
LEFT JOIN `fruit` `f`
ON f.id = pf.fruit_id
GROUP BY
p.id
how can you sort the concatenated left join results?
You can use ORDER BY inside the GROUP_CONCAT.
Also p.name should be in GROUP BY clause
SELECT
p.name,
GROUP_CONCAT(f.name ORDER by f.name ASC SEPARATOR ',') fruit
FROM
person p
LEFT JOIN person_fruit pf
ON p.id = pf.person_id
LEFT JOIN `fruit` `f`
ON f.id = pf.fruit_id
GROUP BY
p.name
Related
I have 3 tables: foods, order_detail, and orders
Here are the records for table foods:
id | name | type
------------------------------
F01 | Omelette | Breakfast
F02 | Burger | Breakfast
F03 | Satay | Lunch
F04 | Fried Rice | Dinner
Here are the records for table order_detail:
food_id | order_id
-----------------------------
F01 | T01
F04 | T01
F02 | T02
F03 | T03
F03 | T04
And here are the records for orders table:
order_id | date | qty
---------------------------------
T01 | 2017-05-01 | 2
T02 | 2017-05-02 | 1
T03 | 2017-05-05 | 1
T04 | 2017-05-07 | 1
I want to show count order detail grouped by food type. I expected this result:
type | total_order
-------------------------
Breakfast | 2
Lunch | 2
Dinner | 1
Here is my approach, but it still doesn't show the expected result.
SELECT
f.type,
(SELECT COUNT(*) FROM order_detail od WHERE f.id = od.food_id) AS total_order
FROM foods f
LEFT JOIN order_detail od ON f.id = od.food_id
GROUP BY f.type
ORDER BY f.id
The result is:
type | total_order
-------------------------
Breakfast | 1
Lunch | 2
Dinner | 1
How can I get the result I want? Thanks in advance!
Aggregation can work here, but you need to join across all three tables:
SELECT f.type, COUNT(o.order_id) AS total_order
FROM foods f
LEFT JOIN order_detail od ON od.food_id = f.id
LEFT JOIN orders o ON o.order_id = od.order_id
GROUP BY f.type
ORDER BY f.id;
Note that we do a left join across all three tables in order to not drop any food type which might happen to have zero orders.
you can add the orderdtails to the subselect to get the correct number
SELECT
f.type,
(SELECT COUNT(*) FROM order_detail od2
WHERE f.id = od2.food_id AND od2.order_id = od.order_id
) AS total_order
FROM foods f
LEFT JOIN order_detail od ON f.id = od.food_id
GROUP BY f.type
ORDER BY f.id
WITH cte AS (
SELECT type
FROM foods
INNER JOIN order_detail ON (order_detail.food_id=foods.id)
INNER JOIN orders ON (order_detail.ord_id=orders.ord_id)
)
SELECT DISTINCT type, COUNT(type) OVER (PARTITION BY type) AS qty
FROM cte
My case looks simple but i'm messing around with this..
I have 4 tables: User, Macros, Categories, and another one that relate users with categories. One Macro have many Categories.
What i need, is a query that based on the Macro, get the users and the Categories where user is NOT IN.
Example: I have a macro named VEICULES, with categories CAR,TRUCK and Motorcycle. User José is on category CAR and User Julio on category CAR and TRUCK, so my query should return:
José | TRUCK,Motorcycle
Julio | Motorcycle
Tables:
prd_users
id | name | Email
---------------------------
1 | José | jose#email.com
2 | Júlio | julio#email.com
3 | André | andre#email.com
cat_macros
macro_id | macro_name
-----------------------
1 | Veicules |
cat_categories
category_id | category_name | macro_id
---------------------------------------
1 | Cars | 1
2 | Trucks | 1
3 | Motorcycles | 1
prd_tr_rabbit_catg
id | category_id | tasker_user_id
---------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
I'm stucked on just getting the categories where the user already is ..
SELECT prd_users.id, prd_users.name,
prd_users.email,cat_macros.macro_name as macro,
GROUP_CONCAT(cat_categories.category_name SEPARATOR ', ') as in_categories
FROM prd_users
INNER JOIN prd_tr_rabbit_catg ON prd_tr_rabbit_catg.tasker_user_id = prd_users.id
INNER JOIN cat_categories ON cat_categories.category_id = prd_tr_rabbit_catg.category_id
INNER JOIN cat_macros ON cat_macros.macro_id = cat_categories.macro_id
WHERE cat_macros.macro_id = '45'
GROUP BY prd_users.id;
To solve this problem it's necessary to create a list of all users joined with all categories for the given macro category. This can be done with a CROSS JOIN:
SELECT *
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
This can then be LEFT JOINed to the prd_tr_rabbit_catg table and by selecting those rows where there is no matching entry in the prd_tr_rabbit_catg table, we can find the users who don't have an entry for the given category:
SELECT c.macro_name, u.id AS user_id, u.name, u.Email, GROUP_CONCAT(c.category_name) AS missing_cats
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
LEFT JOIN prd_tr_rabbit_catg x ON x.tasker_user_id = u.id AND x.category_id = c.category_id
WHERE x.id IS NULL
AND c.macro_id = 1
GROUP BY c.macro_name, u.id
For your sample data, this gives:
macro_name user_id name Email missing_cats
Veicules 1 José jose#email.com Motorcycles,Trucks
Veicules 2 Júlio julio#email.com Motorcycles
Veicules 3 André andre#email.com Cars,Motorcycles,Trucks
Update
To exclude users who don't have any of the categories, add a HAVING clause:
HAVING COUNT(*) < (SELECT COUNT(*) FROM cat_categories WHERE macro_id = 1)
Demo on SQLFiddle
I have 3 tables like these:
product
product_id | name
1 | Pizza Margherita
2 | Pizza Salsiccia
3 | Pizza Marinara
filter_description
filter_id | name
1 | Mozzarella
2 | Pomodoro
3 | Salsiccia
product_filter
product_id | filter_id
1 | 1
1 | 2
2 | 1
2 | 2
2 | 3
3 | 2
I need to join 3 tables and make two queries, the first for all pizza without 'Mozzarella', and the second for all pizza with 'Mozzarella' AND 'Salsiccia'.
You could use below query for your criteria
For all pizza without 'Mozzarella'
select p.product_id, p.name
from product p
join product_filter pf using(product_id)
join filter_description fd using(filter_id)
group by p.product_id, p.name
having sum(fd.name = 'Mozzarella') = 0;
For all pizza with 'Mozzarella' AND 'Salsiccia'
select p.product_id, p.name
from product p
join product_filter pf using(product_id)
join filter_description fd using(filter_id)
group by p.product_id, p.name
having sum(fd.name = 'Mozzarella') = 1
and sum(fd.name = 'Salsiccia') = 1;
Demo
Variants with NOT IN and IN
-- all pizza without 'Mozzarella'
SELECT *
FROM product
WHERE product_id NOT IN(
SELECT DISTINCT pf.product_id
FROM product_filter pf
JOIN filter_description fd ON fd.filter_id=pf.filter_id
WHERE fd.name='Mozzarella'
)
-- all pizza with 'Mozzarella' AND 'Salsiccia'.
SELECT *
FROM product
WHERE product_id IN(
SELECT pf.product_id
FROM product_filter pf
JOIN filter_description fd ON fd.filter_id=pf.filter_id
WHERE fd.name IN('Mozzarella','Salsiccia') -- condition 1
GROUP BY pf.product_id
HAVING COUNT(fd.name)=2 -- condition 2
)
I am a bit confused about using GROUP_CONCAT in MySQL.
To explain, I am working on a listings directory, sort of like Yelp for example. A user filters different criteria and is returned with different places.
For example, if a user wants to see all places that have been categorized (or tagged) as "Restaurant", or maybe "Lounge" then they will see only those places, but also they should be presented with any other tagged categories.
To do this, I have built some tables in my database. The main tables that relate to this question are the tables places and categories and place_category_rel as such:
places table:
+----+------------------+
| id | name |
+----+------------------+
| 1 | Johns Restaurant |
+----+------------------+
| 2 | Jims Place |
+----+------------------+
| 3 | Cafe Luna |
+----+------------------+
place_category_rel table
+----------+-------------+
| place_id | category_id |
+----------+-------------+
| 1 | 1 |
+----------+-------------+
| 1 | 2 |
+----------+-------------+
categories table
+----+------------+
| id | name |
+----+------------+
| 1 | Lounge |
+----+------------+
| 2 | Restaurant |
+----+------------+
| 3 | Night Club |
+----+------------+
To obtain all places I use the following query:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p
LEFT JOIN places_categories_rel rel ON p.id=rel.place_id
LEFT JOIN categories cat ON cat.id=rel.category_id
GROUP BY p.id
Which gets me this result set:
+----+------------------+--------------------+
| id | name | categories |
+----+------------------+--------------------+
| 1 | Johns Restaurant | Lounge,Restaurant |
+----+------------------+--------------------+
| 2 | Jims Place | Null |
+----+------------------+--------------------+
| 3 | Cafe Luna | Null |
+----+------------------+--------------------+
The above works great. However, when I add a WHERE clause to filter down on places that are categorized as "Lounge" , I lose the other category:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p
LEFT JOIN places_categories_rel rel ON p.id=rel.place_id
LEFT JOIN categories cat ON cat.id=rel.category_id
WHERE cat.name = 'Lounge'
GROUP BY p.id
Result:
+----+------------------+------------+
| id | name | categories |
+----+------------------+------------+
| 1 | Johns Restaurant | Lounge |
+----+------------------+------------+
what I would like is this result set instead:
+----+------------------+--------------------+
| id | name | categories |
+----+------------------+--------------------+
| 1 | Johns Restaurant | Lounge, Restaurant |
+----+------------------+--------------------+
Can someone point me in the right direction? Thanks.
If I'm understanding correctly, you are trying to get all categories for a given place if they have a specific category. Assuming so, one option is to use exists:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p
LEFT JOIN places_categories_rel rel ON p.id=rel.place_id
LEFT JOIN categories cat ON cat.id=rel.category_id
WHERE EXISTS (
SELECT 1
FROM places_categories_rel pcr
JOIN categories c ON c.id=pcr.category_id
WHERE name = 'Lounge' AND p.id=pcr.place_id
)
GROUP BY p.id
Not sure you need outer joins in this case either -- probably would work the same with inner joins.
When using a LEFT JOIN, conditions on all but the first table should be in the ON clause:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p LEFT JOIN
places_categories_rel rel
ON p.id = rel.place_id LEFT JOIN
categories cat
ON cat.id = rel.category_id AND cat.name = 'Lounge'
GROUP BY p.id;
Remember: A LEFT JOIN returns all rows in the first table, regardless of whether the ON clause returns true, false, or NULL. If the ON condition is not true, then all columns in the second (and subsequent) tables are NULL.
When you test for cat.name = 'Lounge' in the WHERE clause, cat.name is NULL for the rows that do not match -- hence they are filtered out.
EDIT:
If you only want the category of lounge in the results (with others), then just add a having clause:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p LEFT JOIN
places_categories_rel rel
ON p.id = rel.place_id LEFT JOIN
categories cat
ON cat.id = rel.category_id
GROUP BY p.id
HAVING SUM(cat.name = 'Lounge') > 0;
I think this is the simplest approach.
I need to join tables to do aggregation. I suck at that. Here's my scenario:
CATEGORIES
CatID | CategoryName | Parent
1 | Cars | NULL
2 | Electronics | NULL
3 | DVD | 2
4 | Blu_ray | 2
5 | Shoes | NULL
So basically, topmost elements don't have parents. Then I have
PRODUCTS
ProdID | Prod Name | CatID
1 | DVD Player 1 | 3
2 | Blu-Ray Player | 3
3 | Nike | 5
4 | DVD Player 2 | 3
I want to end up with...
CATEGORIES
CatID | CategoryName | Parent | totalProds
1 | Cars | NULL | 0
2 | Electronics | NULL | 0
3 | DVD | 2 | 2
4 | Blu_ray | 2 | 1
5 | Shoes | NULL | 1
Any ideas?
Aren't you just asking for the total number of products per category?
SELECT CatID, CategoryName, Parent, COUNT(*) totalProds
FROM categories c
INNER JOIN products p ON p.CatID = c.CatID
GROUP BY CatId
See below query
SELECT
c.`CatID`,
c.`CategoryName`,
c.`Parent`,
COALESCE(COUNT(DISTINCT p.`ProdID`),0) AS totalProds
FROM `CATEGORIES` c
LEFT JOIN `PRODUCTS` p
ON p.`CatID` = c.`CatID`
ORDER BY c.`CatID`
LEFT JOIN to return a row for every category. COALESCE to just make sure a 0 is returned if appropriate.
Assuming you have a limited number of category levels, you can union each level at a time. Here's for 4 levels:
SELECT c1.CatID, c1.CategoryName, c1.Parent, COUNT(1) totalProds
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
GROUP BY c1.CatID, c1.CategoryName, c1.Parent
UNION
SELECT c2.CatID, c2.CategoryName, c2.Parent, COUNT(1)
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
INNER JOIN categories c2 ON c2.CatID = c1.Parent
GROUP BY c2.CatID, c2.CategoryName, c2.Parent
UNION
SELECT c3.CatID, c3.CategoryName, c3.Parent, COUNT(1)
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
INNER JOIN categories c2 ON c2.CatID = c1.Parent
INNER JOIN categories c3 ON c3.CatID = c2.Parent
GROUP BY c3.CatID, c3.CategoryName, c3.Parent
UNION
SELECT c4.CatID, c4.CategoryName, c4.Parent, COUNT(1)
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
INNER JOIN categories c2 ON c2.CatID = c1.Parent
INNER JOIN categories c3 ON c3.CatID = c2.Parent
INNER JOIN categories c4 ON c4.CatID = c3.Parent
GROUP BY c4.CatID, c4.CategoryName, c4.Parent
Hope you get the idea...