MySQL - Total of parent + child categories - mysql

I have two tables:
parent-child 'categories':
id name parent_id
1 Food NULL
2 Pizza 1
3 Pasta 2
'transactions':
id amount category_id
1 100 1
2 50 2
3 25 2
I want to return all the Categories along with two total columns:
total = The sum of the amount for all transactions with this category_id
parentTotal = total + the total of all its child categories
Example (using the tables above):
id name parent_id total parentTotal
1 Food NULL 100 175
2 Pizza 1 0 0
3 Pasta 2 75 0
EDIT:
Code updated (based on code from Nedret Recep below) and works fine...
SELECT
tmp1.id, tmp1.name, tmp1.parent_id, tmp1.total, IFNULL(tmp1.total, 0) + IFNULL(tmp2.s, 0) AS parenttotal
FROM
(SELECT
ca.id, ca.name, ca.parent_id, SUM(tr.amount) as total
FROM
categories ca
LEFT JOIN
transactions tr
ON
tr.category_id = ca.id
GROUP BY
ca.id)
AS tmp1
LEFT JOIN
(SELECT
c.id, c.parent_id as categoryid, SUM(t.amount) AS s
FROM
transactions t
RIGHT JOIN
categories c
ON
t.category_id = c.id
GROUP BY
c.parent_id)
AS tmp2
ON tmp2.categoryid = tmp1.id
order by coalesce(tmp1.parent_id, tmp1.id), tmp1.parent_id
I'd really appreciate some help - thanks!

With one inner join we calculate the totals in category which is standard. Then with another inner join we calculate the sums but this time grouping by parent_id. Then we join the two result tables to have both sums in one row. This query will be slow with large tables so an alternative approach on application level would do better.
SELECT
tmp1.id, tmp1.name, tmp1.parent_id, tmp1.total, tmp1.total + tmp2.s AS parenttotal
FROM
(SELECT
ca.id, ca.name, ca.parent_id, SUM(tr.amount) as total
FROM
transactions tr
INNER JOIN
categories ca
ON
tr.categoru_id = ca.id
GROUP BY
ca.id)AS tmp1
LEFT OUTER JOIN
(
SELECT
c.parent_id as categoryid, SUM(t.amount) AS s
FROM
transactions t
INNER JOIN
categories c
ON
t.category_id = c.i
GROUP
BY c.id ) AS tmp2
ON
tmp2.categoryid = tmp.id

Related

how to populate the data from table which unmapped column in join

I am having below tables and want to populate the PAYID from the PAY table by looking in to CATEGORY_ID for below use case
if PAYID is mapped with CATEGORY_ID then return the PAYID
if PAYID is not mapped to CATEGORY_ID then look for BILL_ID and populate is by joining the NETWORK_CATEGORY and CATEGORY table.
If PAYID is not mapped to CATEGORY_ID and even BILL_ID is not associated with NETWORK_CATEGORY_ID then return the CATEGORY_ID as 3 (Uncategorized) for PAY.
Below is the my tables,
PAY Table
PAYID BILL_ID CATEGORY_ID
101 1 1
102 2
103 3
BILL Table
BILL_ID NAME NETWORK_CATEGORY_ID
1 ABC 42
2 XYZ
3 DSC 23
NETWORK_CATEGORY Table
NETWORK_CATEGORY_ID NAME CATEGORY_ID
42 Electric/gas 1
23 ISP 2
CATEGORIES
CATEGORY_ID NAME
1 Utilities
2 Telecom
3 Uncategorized
And i have written below queries ,
select payid(
select payid from PAY p
where p.CATEGORY_ID in (:categoryIds)
UNION
select payid from PAY p
join BILLS b on p.BILL_ID = b.BILL_ID
where NETWORK_CATEGORY_ID in
(
select NETWORK_CATEGORY_ID
from NETWORK_CATEGORY nc
join CATEGORIES c on nc.CATEGORY_ID = c.CATEGORY_ID
where c.CATEGORY_ID in (:categoryIds)
)
and CATEGORY_ID is null
)
My expectation is that if i passed the categoryIds as 3 or (1,2,3 )then it should return the PAYID as 102 and 101,102,103 respectively.
How can i do that.
Or i can say that how can achieve this task,
If PAYID is not mapped to CATEGORY_ID and even BILL_ID is not associated with NETWORK_CATEGORY_ID then return the CATEGORY_ID as 3 (Uncategorized) for PAY.
By using three union i can achieve that but that not looks an efficient solution.
SELECT p.PAYID
FROM PAY p
WHERE p.CATEGORY_ID IN (2,3)
UNION ALL
SELECT p.PAYID
FROM PAY p
JOIN BILL b
ON p.CATEGORY_ID IS NULL AND p.BILL_ID = b.BILL_ID
JOIN NETWORK_CATEGORY nc
ON b.NETWORK_CATEGORY_ID = nc.NETWORK_CATEGORY_ID
WHERE nc.CATEGORY_ID IN (2,3)
UNION ALL
SELECT p.PAYID
FROM PAY p
LEFT JOIN BILL b
ON p.CATEGORY_ID IS NULL AND p.BILL_ID = b.BILL_ID
LEFT JOIN NETWORK_CATEGORY nc
ON b.NETWORK_CATEGORY_ID = nc.NETWORK_CATEGORY_ID
WHERE (3 IN (2,3) AND p.CATEGORY_ID IS NULL AND nc.CATEGORY_ID IS NULL)
Depending on the size of the dataset and the other criteria being applied to the PAY table, this may perform OK -
SELECT p.PAYID
FROM PAY p
LEFT JOIN BILL b
ON p.CATEGORY_ID IS NULL AND p.BILL_ID = b.BILL_ID
LEFT JOIN NETWORK_CATEGORY nc
ON b.NETWORK_CATEGORY_ID = nc.NETWORK_CATEGORY_ID
WHERE p.CATEGORY_ID IN (2,3)
OR nc.CATEGORY_ID IN (2,3)
OR (3 IN (2,3) AND p.CATEGORY_ID IS NULL AND nc.CATEGORY_ID IS NULL)
Messy but 1) first subquery (x) to cater for no entries, 2) first part of union to check category id, 3) UNION to deal with situation where bill_id and category_id are the same, 4) second part of union to find pay via bill, 5) coalesce to cater for no entries.
select coalesce(payid,3) payid,s.`bill/category id`
from
(select 2 `bill/category id` ) x
left join
(select payid ,category_id as `bill/category id` from pay where category_id = 2
union
select payid ,bill.bill_id
from bill
join network_category nc on bill.network_category_id = nc.network_category_id
join pay on pay.bill_id = nc.category_id
where bill.bill_id = 2
and not exists (select payid from pay where category_id = 2)
) s on s.`bill/category id` = x.`bill/category id`

SQL to group and sum subitem and parent rows

I'm having issues trying to figure out how to group and sum subitem with parent item rows.
I have a SQL query that looks like this:
select
topics.name,
sum(commission_amount) as commission
from orders
left join topics
on topics.id = orders.topic_id
group by 1
This works, however I'm trying to group and use only parent topic names.
Topics table:
id
name
topic_id
1
Meal Delivery
NULL
2
Vegan Meal Delivery
1
3
Vegetarian Meal Delivery
1
4
Mattresses
NULL
5
Hybrid Mattress
4
6
Memory Foam Mattress
4
So a parent topic is when topic_id = NULL
Orders table:
id
topic_id
commission_amount
1
1
10
2
2
20
3
3
30
4
4
40
5
5
50
6
6
60
Desired output is this:
name
commission
Meal Delivery
60
Mattresses
150
Join with topics again.
SELECT name, SUM(commission) AS commission
FROM (
-- subtopic commissions
SELECT t1.name, IFNULL(SUM(o.commission_amount), 0) AS commission
FROM topics AS t1
LEFT JOIN topics AS t2 ON t1.id = t2.topic_id
LEFT JOIN orders AS o ON o.topic_id = t2.id
WHERE t1.topic_id IS NULL -- only show parent topics
GROUP BY t1.name
UNION ALL
-- parent topic commissions
SELECT t.name, IFNULL(SUM(o.commission_amount), 0) AS commission
FROM topics AS t
LEFT JOIN orders AS o ON o.topic_id = t.id
WHERE t.topic_id IS NULL
GROUP BY t.name
) AS x
GROUP BY name
Here is a simpler look of the answer using self-join.
SELECT t1.name, SUM(o.commision_amount) AS commision
FROM topics t1
INNER JOIN topics t2 ON t1.id = t2.topic_id OR t1.id = t2.id
INNER JOIN orders o ON t2.id = o.topic_id
WHERE t1.topic_id IS NULL
GROUP BY t1.name;
See db<>fiddle
You can do a self-join to bring all parent topics, and children to the same level:
select TPnt.Name, sum(O.Commission_amount) as Commission_amount
from
Topics TPnt
inner join
Topics TChld
on TPnt.Id=coalesce(TChld.topic_id, TChld.id)
inner join
Orders O
on O.topic_id=TChld.id
group by TPnt.Name

MySQL limit on child

I saw some articles but I can't implement it on my code,
I Have 3 tables
Category
ID
name
1
winter
2
summer
Product
ID
name
1
bikini
2
scarf
Category_product
id_category
id_product
1
1
2
2
Basically like that, with much more products and more records on category_product as well, so what I want is to select 10 categories with their products but limited by 10 as well...
So I have something like
---Category 1
Product 1
Product 2
Product 3
---Category 2
Product 1
Product 2
Product 3
10 category max and 10 products max
I tried this but only get one product
SELECT c.* FROM category c
INNER JOIN category_product cp ON cp.id_category = c.id
INNER JOIN
(SELECT id_category, MAX(id_product) test
FROM category_product
GROUP BY id_category) cp2 ON cp.id_category = cp2.id_category
AND cp.id_product = cp2.test
so what I want is to select 10 categories with their products but limited by 10 as well
You can use row_number() and limit:
select c.*, cp.product_id
from (select c.*
from categories c
limit 10 -- you might want order by to get particular categories
) c join
(select cp.*,
row_number() over (partition by cp.category_id order by cp.product_id) as seqnum
from category_product cp
) cp
on c.category_id = c.id
where seqnum <= 10;
You can join in products if you want additional information about the products.

Select count of total products as well as out of stock products from table

I have to 3 tables: product, product_to_store, store
product table
id quantity status
1 1 1
2 0 1
3 0 1
4 23 1
product_to_store table
store_id product_id
1 1
2 2
1 3
2 4
store table
id name
1 store1
2 store2
To find total products I can run query to fetch all products in table product where status of product is enabled.
select count(*) from product where status=1
total name
2 Store 1
2 store 2
To find total out of stock products I can run below query after joining all 3 tables and using group by store_id:
Select count(*) as outofproducts from product where quantity=0;
Result come like this:
outofproducts name
1 Store 1
1 store 2
But I want combination of above 2 results in single query like below:
outofproducts total name
1 2 Store 1
1 2 store 2
You'd use conditional aggregatiopn, i.e. sum/count over conditions:
select
s.name,
sum(p.quantity > 0) as in_stock,
sum(p.quantity = 0) as out_of_stock,
count(*) as total
from store s
join product_to_store ps on ps.store_id = s.id
join product p on p.id = ps.product_id
group by s.name
order by s.name;
This makes use of MySQL's true = 1, false = 0. If you don't like it, replace sum(p.quantity = 0) with sum(case when p.quantity = 0 then 1 else 0 end) or count(case when p.quantity = 0 then 1 end).
You can start query from store table so that we will get total rows as store table data.
Then use nested query for each store to get out of product and total product count
select
(Select count(*) as outofproducts from product_to_store ps inner join product p on p.id = ps.product_id where quantity=0 and ps.store_id = s.id ) as outofproducts ,
(Select count(*) as count from product_to_store ps inner join product p on p.id = ps.product_id where ps.store_id = s.id ) as totalCount,
s.name
from store s
You could join the related subquery for count
select t1.name, t1.outofproducts, t2.total
from(
select b.id, b.name , count(*) outofproducts
from product_to_store c
inner join product a on a.id = c.product_id
inner join store b on a.id = c.store_id
where a.quantity = 0
group by b.id, b.name
) t1
inner join (
select b.id, b.name , count(*) total
from product_to_store c
inner join product a on a.id = c.product_id
inner join store b on a.id = c.store_id
group by b.id, b.name
) t2 on t1.id = t2.id

Normalize data in SQL query

I have an SQL query A (see below for more details) that returns a table as following:
cluster brand amount
0 bos 600
0 phi 300
0 har 100
1 pro 2500
1 wal 1500
1 ash 1000
2 dil 4200
2 sor 500
2 van 300
...
However, I want to show not the amount, but the fraction of that amount compared to the total amount in that cluster, like in the following table:
cluster brand amount
0 bos 0.60
0 phi 0.30
0 har 0.10
1 pro 0.50
1 wal 0.30
1 ash 0.20
2 dil 0.84
2 sor 0.10
2 van 0.06
...
How should I change my SQL such that I can get access to the sum over all amounts in one cluster, and still have multiple rows with the same cluster?
** Details **
SQL server: MySQL, interfaced through the python-MySQL connector.
Current SQL query to generate the first table:
SELECT c.cluster, brand, COUNT(o.id) AS brand_amount
FROM nyon_all.clustering AS c
LEFT JOIN nyon_all.persons AS p ON c.pid = p.id
LEFT JOIN nyon_all.orders AS o ON p.id = o.pid
LEFT JOIN nyon_all.articles AS a ON o.aid = a.id
LEFT JOIN nyon_all.brands AS ab ON a.brand_id = ab.id
WHERE c.cluster_round = 'Org_2014-08-27_10:45:35'
GROUP BY cluster, brand
HAVING brand_amount > 100
ORDER BY c.cluster ASC, brand_amount DESC;
Table orders (primary key id) links persons (foreign key pid) with articles (foreign key aid). Articles have a certain brand (foreign key brand_id), which are related to a name in the Table brands.
The total amount of articles per cluster can be retrieved with the following SQL query:
SELECT c.cluster, COUNT(o.pid) AS amount
FROM nyon_all.clustering AS c
LEFT JOIN nyon_all.persons AS p ON c.pid = p.id
LEFT JOIN nyon_all.orders AS o ON p.id = o.pid
WHERE c.cluster_round = 'Org_2014-08-27_10:45:35'
GROUP BY cluster
ORDER BY c.cluster ASC, amount DESC;
Result:
cluster amount
0 1000
1 5000
2 5000
However, I can't seem to combine the two SQL queries.
You could do a join on a subquery summing the amount by cluster
select t1.cluster, amount / sumAmount
from Table1 t1
join (select cluster, sum(amount) as sumAmount
from Table1
group by cluster)s
on t1.cluster = s.cluster
see SqlFiddle
EDIT
SELECT
c.cluster,
brand,
COUNT(o.id) / coalesce(s.sumBrandAmount, 0) AS brand_amount -- of course it would be nice to check for dividing by 0
FROM nyon_all.clustering AS c
LEFT JOIN nyon_all.persons AS p ON c.pid = p.id
LEFT JOIN nyon_all.orders AS o ON p.id = o.pid
LEFT JOIN nyon_all.articles AS a ON o.aid = a.id
LEFT JOIN nyon_all.brands AS ab ON a.brand_id = ab.id
LEFT JOIN (select c1.id, count(o1.id) as sumBrandAmount
from nyon_all.clustering c1
left join nyon_all.persons p1 on p1.id = c1.pid
left join nony_all.orders as o1 on o1.id = p1.id
--maybe some where clause as in your main query
group by c1.id) s
ON s.id = c.id
WHERE c.cluster_round = 'Org_2014-08-27_10:45:35'
GROUP BY cluster, brand
HAVING brand_amount > 100
ORDER BY c.cluster ASC, brand_amount DESC;