Joining two tables with MySQL for aggregation? - mysql

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...

Related

MySQL Query JOIN 3 tables

Looking to join 3 tables, but having trouble on the last one.
members
ID | name
---------
1 | John
2 | Jane
3 | Jack
member_points (can have multiple transactions between members)
ID | date | id_from | id_to
---------------------------
1 | 8/8 | 1 | 2
2 | 8/8 | 1 | 2
3 | 8/8 | 3 | 2
member_ratings (one member can only rate another member, one time)
ID | id_from | id_to | rating
-----------------------------
1 | 2 | 1 | 5
Each member may rate each member only once, and can only rate the member they received a point from, based on the member_points table.
My current query achieves this, however I'm having difficulty introducing the 3rd table, that will include the rating accoringly.
Here is what I have so far:
$sql = '
SELECT *,
m.id AS id,
c1.id AS id_from,
c1.name AS name_from,
c2.id AS id_to,
c2.name AS name_to
FROM member_points AS m
JOIN members AS c1 ON m.id_from = c1.id
JOIN members AS c2 ON m.id_to = c2.id
and m.id_to='.$_SESSION["userid"].'
GROUP BY name_from';
My goal is join the 3rd table so I can call the associated rating.
ID | name_from | name_to | rating
----------------------------------
1 | 2 | 1 | 5
2 | 2 | 3 | pending
#Andrew you need to use a LEFT JOIN to join in the member_ratings table like this:
$sql = '
SELECT *,
m.id AS id,
c1.id AS id_from,
c1.name AS name_from,
c2.id AS id_to,
c2.name AS name_to,
IF(mr.rating IS NULL, 'pending', mr.rating) AS rating
FROM member_points AS m
JOIN members AS c1 ON m.id_from = c1.id
JOIN members AS c2 ON m.id_to = c2.id
and m.id_to='.$_SESSION["userid"].'
LEFT JOIN member_ratings AS mr ON mr.id_from = c1.id
AND mr.id_to = c2.id
GROUP BY name_from';
please let me know if this isn't what you need and I'll try to help you further.

MYSQL - Group Contact rows with records NOT IN

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

MySQL: left join only one row and sum

It's possible left join only one row without sub query?
I need to get product statistics and some of products have multiple groups.
Therefore, the amount of products is incorrect.
SELECT COUNT(p.id) AS total_product, SUM(p.price) AS total_price
FROM product p
LEFT JOIN attribute_group a ON
a.product_id = p.id
WHERE p.created_at >= "2018-01-01" AND (a.id = 1 OR a.id = 2)
GROUP BY p.id
LIMIT 0, 30;
product
id | price
1 | 100
2 | 150
3 | 250
attribute_group
id | product_id | title
1 | 1 | a1
2 | 1 | a2
3 | 2 | a3
4 | 3 | a4
Should be:
1| 100
But i get:
2 | 200
You appear to want all products or the counts/sum of them that have attributes of both 1 and 2. Here is one method:
SELECT COUNT(*) as num_products, SUM(p.price) as total_price
FROM product p
WHERE p.created_at >= '2018-01-01' AND
EXISTS (SELECT 1
FROM attribute_group ag
WHERE ag.product_id = p.id AND ag.id = 1
) AND
EXISTS (SELECT 1
FROM attribute_group ag
WHERE ag.product_id = p.id AND ag.id = 2
);

MySQL show relations MANY-MANY by condition

There are two tables:
partner
id | name
--------------
1 | partner_1
2 | partner_2
3 | partner_3
4 | partner_4
contract
id | name | is_active
---------------------------
1 | contract_1 | 1
2 | contract_2 | 0
3 | contract_3 | 1
4 | contract_4 | 0
5 | contract_5 | 0
There is a third table that relates the previous two tables with many-to-many relationship
partner_contract
partner_id | contract_id
------------------------
1 | 1
1 | 2
2 | 3
2 | 2
2 | 4
3 | 5
Each partner can have several contracts, among which ONLY ONE can be active and some inactive .
Also the partner may not have contract at all.
I need a query that displays all the partners together with the active contract. If partner dont' have an active contract, display NULL.
partner_id | partner_name | contract_name
-----------------------------------------
1 | partner_1 | contract_1
2 | partner_2 | contract_3
3 | partner_3 | NULL
4 | partner_4 | NULL
I found a solution, but it seems to me that it is not perfect .
SELECT
p.id AS partner_id,
p.name AS partner_name,
active_contract.name AS contract_name
FROM partner p
LEFT JOIN (
SELECT *
FROM contract c
LEFT JOIN partner_contract pc on pc.contract_id = c.id
WHERE c.is_active = 1
) active_contract
ON active_contract.partner_id = p.id
Is there a more elegant solution?
Ray's (deleted) query is close to the right solution. The condition on the contract should go in the on clause, not the where clause:
SELECT p.id AS partner_id, p.name AS partner_name, c.name AS contract_name
FROM partner p LEFT JOIN
partner_contract pc
ON p.id = pc.partner_id LEFT JOIN
contract c
ON pc.contract_id = c.id AND c.is_active = 1;
EDIT:
Okay, the above is wrong. This can be fixed with a group by:
SELECT p.id AS partner_id, p.name AS partner_name, MAX(c.name) AS contract_name
FROM partner p LEFT JOIN
partner_contract pc
ON p.id = pc.partner_id LEFT JOIN
contract c
ON pc.contract_id = c.id AND c.is_active = 1
GROUP BY p.id, p.name;
The more elegant solution (in my opinion):
SELECT p.*,
(select name
from partner_contract pc join
contract c
on pc.contract_id = c.id AND c.is_active = 1
where p.id = pc.partner_id
) as contract_name
FROM partner p;
SQL Fiddle
This can take advantage of indexes and does not require aggregation.

How to mysql distinct one of many count fields when GROUP BY doesn't work?

I've a database with products, manufactors and categories of this products and information about name of manufactors, products and if those products have images.
But in this database are duplicated products with different IDs. Only thing I can identify them is their name.
Now I've a query where I want see how many products per manufactorer and per category were in my database and count also the number of products with images.
-----------------------------------------------------
| manufactor | category | products | productsImages |
|------------|----------|----------|----------------|
| manu-1 | cat-1 | 5 | 3 |
|------------|----------|----------|----------------|
| manu-1 | cat-2 | 15 | 8 |
|------------|----------|----------|----------------|
| manu-2 | cat-1 | 11 | 0 |
|------------|----------|----------|----------------|
| manu-3 | cat-2 | 5 | 4 |
|------------|----------|----------|----------------|
| manu-3 | cat-3 | 9 | 4 |
|------------|----------|----------|----------------|
My approach looks like:
SELECT m.`name` AS manufactor,
pg.`name` AS category,
COUNT(p.`name`) AS products,
COUNT(pi.`idImage`) AS productsImages
FROM `product` AS p
LEFT JOIN `product_image` AS pi ON pi.`idProduct` = p.`id`
INNER JOIN `manufacturer` AS m ON m.`id` = p.`idManufacturer`
INNER JOIN `product_groupname` AS pg ON pg.`id` = p.`idProductGroup`
GROUP BY p.`idManufacturer`, p.`idProductGroup`
ORDER BY m.`name`, p.`idProductGroup`;
I can't group by p.name because then I'd get a result row for each product.
Do COUNT(DISTINCT(p.name)) didn't help either.
So any suggestions or will I have do to subqueries?
If you want to count the distinct names where pi.`idImage` IS NOT NULL you can use the following:
SELECT m.`name` AS manufactor,
pg.`name` AS category,
COUNT(DISTINCT p.`name`) AS products,
COUNT(DISTINCT CASE
WHEN pi.`idImage` IS NOT NULL THEN p.`name`
ELSE NULL
END) AS productsImages
FROM `product` AS p
LEFT JOIN `product_image` AS pi ON pi.`idProduct` = p.`id`
INNER JOIN `manufacturer` AS m ON m.`id` = p.`idManufacturer`
INNER JOIN `product_groupname` AS pg ON pg.`id` = p.`idProductGroup`
GROUP BY p.`idManufacturer`, p.`idProductGroup`
ORDER BY m.`name`, p.`idProductGroup`;
See comments on question - distinct on p.name is the solution.
SELECT m.`name` AS manufactor,
pg.`name` AS category,
COUNT(DISTINCT(p.`name`)) AS products,
COUNT(pi.`idImage`) AS productsImages
FROM `product` AS p
LEFT JOIN `product_image` AS pi ON pi.`idProduct` = p.`id`
INNER JOIN `manufacturer` AS m ON m.`id` = p.`idManufacturer`
INNER JOIN `product_groupname` AS pg ON pg.`id` = p.`idProductGroup`
GROUP BY p.`idManufacturer`, p.`idProductGroup`
ORDER BY m.`name`, p.`idProductGroup`;