table joins with multiple group_concat - mysql

I have a problem regarding joining tables with group_concat. Here are the details.
table_orders:
item_cd order_id descs quantity status seq_no
1 100 coca-cola 2 A 232
2 100 pizza 1 A 233
3 101 cheeseburger 5 A 234
4 102 pepsi 4 A 235
4
table_instructions:
item_cd instruction
3 more cheese
3 less vegetable
cancelled_item_table:
quantity seq_no
1 234
1 234
1 235
Now what I want to achieve is like this:
item_cd descs quantity instructions cancelled_item
1 coca-cola 2 - -
2 pizza 1 - -
3 cheeseburger 2 more cheese, less vegetable 1,1
4 pepsi 4 - 1
This is my current query:
SELECT
ord.item_cd,
ord.order_id,
ord.descs,
ord.quantity,
GROUP_CONCAT(x.quantity) as cancelled,
GROUP_CONCAT(i.instruction) as instruct
FROM table_orders ord
LEFT JOIN cancelled_item_table x ON ord.seq_no = x.seq_no
LEFT JOIN table_instructions i ON ord.item_cd = i.item_cd
WHERE ord.status = 'A'
GROUP BY ord.order_id
and here is the output:
item_cd descs quantity instructions cancelled_item
1 coca-cola 2 - 1
2 pizza 1 - 1
3 cheeseburger 2 more cheese, more cheese,
less vegetable, less vegetable 1,1,1,1
4 pepsi 4 - 1
If you notice, cheeseburger has 2 cancelled item and 2 instruction, but the output is 4, looks like it's multiplying.

Since the join with cancelled_item_table multiplies rows, you have to join to an already grouped subquery, like this:
SELECT
ord.item_cd,
ord.order_id,
ord.descs,
ord.quantity - coalesce(x.tot,0) as quantity,
GROUP_CONCAT(i.instruction) as instruct,
x.cancelled
FROM
table_orders ord LEFT JOIN table_instructions i
ON ord.item_cd = i.item_cd LEFT JOIN
(select seq_no, count(*) as tot, GROUP_CONCAT(quantity) as cancelled
from cancelled_item_table
group by seq_no) x ON ord.seq_no = x.seq_no
WHERE ord.status = 'A'
GROUP BY ord.item_cd, ord.order_id, ord.descs, quantity

Related

count and sum function not working in left join

I have the following 3 tables and i would like to know the correct sql for the expected result as below.
my sql here is not working;
select h.pid,
h.name,
sum(r.amount1) as total1,
sum(r.amount2) as total2,
count(g.pid) as times,
sum(g.take) as totaltaken
from history h
left join rpt_revenue r on h.pid=r.pid
left join guest g on g.pid=r.pid
group by h.pid, h.name;
history
pid name
1 peter
2 may
rpt_revenue
id pid amount1 amount2
1 1 10.00 11.00
2 2 20.00 20.00
3 1 2.00 2.00
4 2 2.00 2.00
guest
gid pid id take
1 1 1 2
2 1 3 2
3 2 2 3
expected result
pid total1 total2 times totaltaken
1 12.00 13.00 2 4
2 22.00 22.00 1 3
So to be able to use aggregate function over join, you should first aggregate your data in a join subquery and then aggregate all of them at the top level
here some examples of aggregation

For each day list the best selling item in each category including total unit sold and revenue

I have a purchase table like below
customerID orderID item category purDate sold price
1 001 book stationary 1/1/2018 1 5
2 002 ball toys 1/1/2018 2 2
3 003 shirt cloth 1/1/2018 1 10
1 004 pen stationary 1/1/2018 5 3
4 005 shirt cloth 1/1/2018 2 10
5 006 card stationary 1/2/2018 15 2
6 007 tshirt cloth 1/2/2018 3 7
2 008 book stationary 1/3/2018 6 5
3 009 car toys 1/3/2018 2 4
1 010 book stationary 1/3/2018 4 5
4 011 ball toys 1/4/2018 2 2
6 012 pen stationary 1/4/2018 2 3
7 013 notebk stationary 1/4/2018 2 3
and I would like to query the best selling item for each day and category using MySQL so the query result would look like
purDate category item total revenue
1/1/2018 stationary pen 5 15
1/1/2018 toys ball 2 4
1/1/2018 cloth shirt 3 30
1/2/2018 stationary card 15 30
1/2/2018 cloth tshirt 3 21
1/3/2018 stationary book 10 50
1/3/2018 toys car 2 8
1/4/2018 toys ball 2 4
1/4/2018 stationary pen 2 6
1/4/2018 stationary notebk 2 6
I've checked other related topics on stackoverflow but couldn't find what I'm looking for. I am trying to use window function with aggregate functions, but keep failing.
SELECT purDate, category, item, total, revenue
FROM (SELECT purDate, category, item, SUM(sold) AS total, sold * price AS revenue,
RANK() OVER(PARTITION BY purDate, category ORDER BY SUM(sold) DESC) AS rank
GROUP BY 1,2,3) q
WHERE q.rank = 1
Thank you for your help in advance.
You miss a FROM in your inner query. And you missed to also group by price and to build the revenue as sum(sold) * price. Other than that you were absolutely on the right path.
SELECT purdate,
category,
item,
total,
revenue
FROM (SELECT purdate,
category,
item,
sum(sold) total,
sum(sold) * price revenue,
rank() OVER (PARTITION BY purdate
ORDER BY sum(sold) DESC) r
FROM purchase
GROUP BY purdate,
category,
item,
price) x
WHERE r = 1
ORDER BY purdate;
db<>fiddle
Dont be afraid to break what you are trying to accomplish into multiple steps instead of one gigantic query.
Create table bleach select
pur_date, max(sold) as max_sold,
'' as total_rev
from your_table group by
pur_date;
Alter table bleach add
index(pur_date);
Alter table bleach add index
(max_sold);
Select a.* from your_table a,
bleach b where
a.pur_date=b.pur_date and
a.max_sold=b.max_sold;
Update bleach set
total_rev=sold*price
Drop table bleach;

Subquery or join to match 2 tables on 2 fields on a range of values

I want for each unique pur_dates, how many items from the product_sold field was equal to items in productfield WHERE chk_date was within 31 days of the pur_date DIVIDE by total number of product_sold in that 31 days.
The 2 big conditions are that items in product_sold is equal to item in product and for the match to be valid, the date range from table2 must be within a month of table1.
The product field is unique while the product_sold field can have repeating products. The pur_date field only varies on year and month.
Table1
pur_date product
2015-07-01 shirt
2015-06-01 shoe
2015-04-01 purse
2015-04-01 bag
2014-05-01 key
2015-05-01 gloves
Table2
chk_date cost product_sold
2015-07-29 9 bag
2015-07-15 10 shoe
2015-06-30 8 shirt
2014-06-25 6 bag
2015-06-01 9 shirt
2015-05-28 8 shoe
2015-05-15 4 key
2015-04-28 5 shirt
2015-03-15 6 purse
2015-03-15 4 ring
2015-03-10 4 key
2015-03-01 2 bag
I have a non-working subquery to do something like this:
Select pur_date,
(Select SUM(CASE WHEN product_sold IN (select product from table1) THEN 1 ELSE 0 END)/COUNT(product_sold)
from table2
where (pur_date - chk_date) <=31) AS percent_sold
from (select distinct pur_date from table1) t;
Error i got was: Illegal expression in WHEN clause of CASE
Example:
output_table
pur_date num_product_match
2015-07-01 2/3 <--for the 3 product_sold in June,2 items(shirt) match product = shirt
2015-06-01 1/2 <-- for the 2 product_sold in May,1 item(shoe) match product = shoe
2015-05-01 1/1 <-- for th 1 product_sold in April, 1 item(shirt) match product = shirt
2015-04-01 2/4 <-- for the 4 product sold in March, 2 item(purse, bag) match product = purse and product = bag
SELECT pd.pur_date,
SUM(CASE WHEN t1.product IS NOT NULL THEN 1 ELSE 0 END) /
CAST(COUNT(t2.product_sold) as float) As num_product_match
FROM (SELECT DISTINCT pur_date FROM Table_1) pd
INNER JOIN Table_2 t2 ON t2.chk_date < pd.pur_date
AND t2.chk_date >= DATE_ADD(pd.pur_date, INTERVAL -1 MONTH)
LEFT JOIN Table_1 t1 ON t1.pur_date > t2.chk_date
AND t1.pur_date <= DATE_ADD(t2.chk_date, INTERVAL 1 MONTH)
AND t1.product = t2.product_sold
GROUP BY pd.pur_date

MySQL join and count according to its column value

If I have a MySQL table looking something like this:
breeds
id name
-------------------------------
1 Labrador
2 Jack Russel Terrier
3 Shetland Sheepdog
And a MySQL table looking like this:
dogs
id owner breed sex
-----------------------------------
1 Sara 1 f
2 Kent 1 f
3 Billy 1 m
4 Joe 2 f
5 Billy 2 m
Is it possible to run a MySQL query to get output like this:
id name females males
------------------------------------------------
1 Labrador 2 1
2 Jack Russel Terrier 1 1
3 Shetland Sheepdog 0 0
I would like to have a JOIN or similar that count the number of females/males from the dogs table.
You can do this:
SELECT b.id,b.name,
IFNULL(SUM(CASE WHEN sex='f' THEN 1 ELSE 0 END),0) as females,
IFNULL(SUM(CASE WHEN sex='m' THEN 1 ELSE 0 END),0) as males
FROM breeds b LEFT JOIN
dogs d on b.id=d.breed
GROUP BY b.id,b.name
Explanation:
using LEFT JOIN will include the record eventhough there is male/female count. IFNULL will replace the null value with 0.
Result:
id name females males
-------------------------------------
1 Labrador 2 1
2 Jack Russel Terrier 1 1
3 Shetland Sheepdog 0 0
Sample result in SQL Fiddle.
Or alternatively:
SELECT id, name,
(SELECT COUNT(*) FROM dogs WHERE breed=b.id AND sex='f') females,
(SELECT COUNT(*) FROM dogs WHERE breed=b.id AND sex='m') males
FROM breeds b
see here: http://www.sqlfiddle.com/#!9/03da0/1

Add total of 3 rows for specific id

I have three tables:
Students
-------------------------------------------------------------
studentId first last gender weight
-------------------------------------------------------------
1 John Doe m 185
2 John Doe2 m 130
3 John Doe3 m 250
Lifts
-------------------
liftId name
-------------------
1 Bench Press
2 Power Clean
3 Parallel Squat
4 Deadlift
5 Shoulder Press
StudentLifts
------------------------------------------------
studentLiftId studentId liftId weight
------------------------------------------------
1 1 1 185
2 2 3 130
3 3 1 190
4 1 2 120
5 2 1 155
6 3 2 145
7 1 1 135
8 1 1 205
9 2 3 200
10 1 3 150
11 2 2 110
12 3 3 250
I would like to have four top lists:
Bench Press
Parallel Squat
Power Clean
Total of the above 3
I can successfully grab a top list for each specific lift using the following query:
SELECT s.studentId, s.first, s.last, s.gender, s.weight, l.name, sl.weight
FROM Students s
LEFT JOIN (
SELECT *
FROM StudentLifts
ORDER BY weight DESC
) sl ON sl.studentId = s.studentId
LEFT JOIN Lifts l ON l.liftId = sl.liftId
WHERE l.name = 'Bench Press'
AND s.gender = 'm'
AND s.weight > 170
GROUP BY s.studentId
ORDER BY sl.weight DESC
However, I am stuck on how to add the highest total of each lift for each student. How can I first find the highest total for each student in each lift, and then add them up to get a total of all three lifts?
Edit
The result set that I am looking for would be something like:
-------------------------------------------------
studentId first last weight
-------------------------------------------------
3 John Doe3 585
1 John Doe 475
2 John Doe2 465
I also forgot to mention that I would actually like two lists, one for students above 170 and one for students below 170.
SELECT -- join student a total weight to the student table
A.studentId,
A.first,
A.last,
C.totalWeight
FROM
Student A,
(
SELECT -- for each studet add the max weights
sum(B.maxWeight) as totalWeight,
B.studentID
FROM (
SELECT -- for each (student,lift) select the max weight
max(weight) as maxWeight,
studentId,
liftID
FROM
StudentLifts
GROUP BY
studentId,
liftID
) B
GROUP BY
studentId
) C
WHERE
A.studentID = C.studentId
-- AND A.weight >= 170
-- AND A.weight < 170
-- pick one here to generate on of the two lists.