How to join three tables to get Sum - mysql

I have three tables: Products, Purchase, Invoice
Product table:
Producct_no Name
1 A
2 B
3 C
Purchase table:
Purchase_no Product_no Qty
001 1 81
002 2 150
003 3 10
Invoice table:
Invoice_no Product_no Qty
001 1 20
002 2 10
003 3 10
I want to get each product's purchase quantity and invoice quantity, I used following query
SELECT PR.product_no, sum(P.qty),sum(I.qty)
FROM products PR
LEFT JOIN invoice I ON I.product_no=PR.product_no
LEFT JOIN purchase P ON P.product_no=PR.product_no
group by PR.product_no
product_no sum(P.qty) sum(I.qty)
001 162 160
002 150 50
003 10 10
EDIT: Expected results
product_no sum(P.qty) sum(I.qty)
001 81 20
002 150 10
003 10 10
My query is giving me wrong response (sum of quantities are wrong), please help me to correct my query to get the results properly. thanks

I don't think your sample data is really what you have based on the information provided. My best guess here is that your query is doing a fan-out on either or both of those joins which is messing up your sums. You need to sum them separately, else additional rows in either on of those joins will fan out the other join, duplicating your results in the sum. This is evident in your result since 001 looks to be double (even though your sample data doesn't show it).
Something like this would ensure sums independent of each other:
SELECT PR.product_no,
( SELECT sum(I.qty)
FROM invoice I
WHERE I.product_no=PR.product_no ) invoice_qty,
( SELECT sum(P.qty)
FROM purchase P
WHERE P.product_no=PR.product_no ) purchase_qty
FROM products PR

I think you have a problem with GROUP BY there. I would do something like this in this case
SELECT P.Product_no, Inv.InvProdSum, Pur.PurProdSum
FROM Product P
LEFT JOIN (SELECT Product_no, SUM(Qty) AS InvProdSum
FROM Invoice
GROUP BY Product_no) AS Inv
ON P.Product_no = Inv.Product_no
LEFT JOIN (SELECT Product_no, SUM(Qty) AS PurProdSum
FROM Purchase
GROUP BY Product_no) AS Pur
ON P.Product_no = Pur.Product_no
Here is SQL FIddle for that http://sqlfiddle.com/#!9/731a5/1
NOTE i add some extra value here to test how it's work...
GL!

Here is my solution without subqueries:
with
product(product_no, name) as (values
(1, 'A'),
(2, 'B'),
(3, 'C')
),
purchase(purchase_no, product_no, qty) as (values
('001', 1, 81),
('002', 2, 150),
('003', 3, 10),
('004', 1, 1000)
),
invoice(invoice_no, product_no, qty) as (values
('001', 1, 20),
('002', 2, 10),
('003', 3, 10),
('004', 2, 5000)
),
mixed as
(select d.product_no,
purchase_no, p.qty as qty_purchase,
invoice_no, i.qty as qty_invoice,
row_number() over(partition by purchase_no) as rn_purchase,
row_number() over(partition by invoice_no) as rn_invoice
from product d
left join purchase p on p.product_no = d.product_no
left join invoice i on i.product_no = d.product_no
)
select product_no,
sum((case when rn_purchase = 1 then 1 else 0 end) * qty_purchase) as sum_purchase,
sum((case when rn_invoice = 1 then 1 else 0 end) * qty_invoice) as sum_invoice
from mixed
group by product_no
order by product_no
;
And results:
product_no|sum_purchase|sum_invoice|
----------|------------|-----------|
1| 1081| 20|
2| 150| 5010|
3| 10| 10|

Related

Using group_concat With a Condition: Display 0 if Values are Not Equal

I just want to display column fields horizontally but also putting a condition to it. Display zero if it has not met the condition.
Example problem: Find the PAYCODE 912 and 686 and display the amount, if not available, display 0
my_table
EMPLOYEE
PAYCODE
AMOUNT
1
912
1
1
123
3
2
912
5
2
686
7
3
111
4
Output must be:
EMPLOYEE
AMOUNT
1
1,0
2
5,7
3
0,0
My code so far:
SELECT
EMPLOYEE,
GROUP_CONCAT(DISTINCT CONCAT(
IF(PAYCODE = '912', AMOUNT, '0'),
IF(PAYCODE = '686', AMOUNT, '0'))
SEPARATOR',') as AMOUNT
FROM
my_table
Note: There are no duplicate paycodes on a similar employee. e.g. two 912 paycodes
I'm thinking a cross join approach should work here:
SELECT e.EMPLOYEE,
GROUP_CONCAT(COALESCE(t.AMOUNT, 0) ORDER BY e.PAYMENTTYPE DESC) AS AMOUNT
FROM (SELECT DISTINCT EMPLOYEE FROM my_table) e
CROSS JOIN (SELECT '912' AS PAYMENTTYPE UNION ALL SELECT '686') p
LEFT JOIN my_table t
ON t.EMPLOYEE = e.EMPLOYEE AND
t.PAYMENTTYPE = p.PAYMENTTYPE
GROUP BY e.EMPLOYEE;
The cross join between the e and p subqueries generates all employee/payment type combinations of interest (only types 912 and 686). We then left join to your table to bring in the amounts, which if are missing we report 0 instead.

Get sold products within date range and order them by sold quantity

I have this table scheme (removed non-important columns):
and I am programming REST API on that. I need to get paginated products sold within some date range.
This wouldn't be a problem for me, but I also need to sort them by either product's code or its sold quantity. The latter is a problem for me.
My idea was to query products and then use subquery to find their sold_products filtered by date and sum the resulting sold quantities. Then order by that sum.
This works but this really is not efficient. For every single product I have to sum it's sold products and in the end I only take 10 results (because I'm using pagination). My latest attempt using this way took 6.5 seconds, so...
My second idea was to go from the other side. Query sold_products, group them by product_id, filter them and then find it's parents.
This works well, this would be exactly it, but there is one problem - I don't get products which were not sold yet.
How can I do this? I think I won't need exact query, just some idea how should I approach this should be enough.
Thank you in advance!
EDIT:
Input data in CSV (hope it's ok like this):
product:
id
code
1
A11
2
A12
3
B11
product_variant:
id
product_id
code
initial_quantity
1
1
A11-1
50
2
1
A11-2
50
3
1
A11-3
80
4
2
A12-1
20
5
2
A12-2
30
6
2
A12-3
80
7
2
A12-4
90
8
3
B11-1
70
9
3
B11-2
70
sold_product:
id
product_id
product_variant
quantity
date
1
1
1
20
2021-04-01
2
1
1
15
2021-04-01
3
1
2
15
2021-04-04
4
1
3
10
2021-04-05
5
1
3
19
2021-04-07
6
2
4
11
2021-04-07
7
2
5
12
2021-04-08
8
2
7
15
2021-04-10
9
2
7
15
2021-04-10
Result:
product_id
product_code
initial_quantity
sold_quantity
1
A11
180
79
2
A12
220
53
3
B11
140
0 or NULL
Result when sold date range 2021-04-07 to 2021-04-08 and ordered by sold_quantity asc:
product_id
product_code
initial_quantity
sold_quantity
3
B11
140
0 or NULL
1
A11
180
19
2
A12
220
23
Sample data SQL:
CREATE TABLE product(id INT, code VARCHAR(25));
CREATE TABLE product_variant(id INT, product_id INT, code VARCHAR(25), initial_quantity INT);
CREATE TABLE sold_product(id INT, product_id INT, product_variant_id INT, quantity INT, date_time DATETIME);
INSERT INTO product VALUES(1,'A11');
INSERT INTO product VALUES(2,'A12');
INSERT INTO product VALUES(3,'B11');
INSERT INTO product_variant VALUES(1,1,'A11-1',50);
INSERT INTO product_variant VALUES(2,1,'A11-2',50);
INSERT INTO product_variant VALUES(3,1,'A11-3',80);
INSERT INTO product_variant VALUES(4,2,'A12-1',20);
INSERT INTO product_variant VALUES(5,2,'A12-2',30);
INSERT INTO product_variant VALUES(6,2,'A12-3',80);
INSERT INTO product_variant VALUES(7,2,'A12-4',90);
INSERT INTO product_variant VALUES(8,3,'B11-1',70);
INSERT INTO product_variant VALUES(9,3,'B11-2',70);
INSERT INTO sold_product VALUES(1,1,1,20,'2021-04-01');
INSERT INTO sold_product VALUES(2,1,1,15,'2021-04-01');
INSERT INTO sold_product VALUES(3,1,2,15,'2021-04-04');
INSERT INTO sold_product VALUES(4,1,3,10,'2021-04-05');
INSERT INTO sold_product VALUES(5,1,3,19,'2021-04-07');
INSERT INTO sold_product VALUES(6,2,4,11,'2021-04-07');
INSERT INTO sold_product VALUES(7,2,5,12,'2021-04-08');
INSERT INTO sold_product VALUES(8,2,7,15,'2021-04-10');
INSERT INTO sold_product VALUES(9,2,7,15,'2021-04-10');
First idea:
SELECT p.id, p.code,
(
SELECT SUM(v.initial_quantity)
FROM product_variant AS v
WHERE p.id = v.product_id
) AS initial_quantity,
(
SELECT SUM(s.quantity)
FROM sold_product AS s
WHERE p.id = s.product_id
) AS sold_quantity
FROM product AS p
ORDER BY sold_quantity;
Second idea:
SELECT p.id, p.code,
(
SELECT SUM(v.initial_quantity)
FROM product_variant AS v
WHERE p.id = v.product_id
) AS initial_quantity,
SUM(s.quantity) AS sold_quantity
FROM sold_product AS s
INNER JOIN product AS p ON s.product_id = p.id
GROUP BY p.code, p.id,
(
SELECT SUM(v.initial_quantity)
FROM product_variant AS v
WHERE p.id = v.product_id
)
ORDER BY sold_quantity;
EDIT: JOIN attempt, returns single result with sum of all sold quantities
SELECT p.id, p.code,
(
SELECT SUM(v.initial_quantity)
FROM product_variant AS v
WHERE p.id = v.product_id
) AS initial_quantity,
SUM(s.quantity) AS sold_quantity
FROM product AS p
JOIN sold_product AS s ON p.id = s.product_id
ORDER BY sold_quantity;
Solved - missing index key on sold_product.product_id. I never thought missing index key could slow queries that much, up to 30 seconds. With that index key it takes 0.1s.
Thank you all who tried to help!

Performing a Union on groups of rows without the same columns. Trying to generate empty histogram bins

I am using MySQL.
I have a table grades, that looks like so:
course_id grade
101 90
101 100
101 100
102 92
102 85
102 90
I have a view that counts how many grades are in each bin, for each class using the following query.
SELECT
*,
COUNT(grade_bin) as count
FROM
(SELECT
course_id,
FLOOR(grade/5.00)*5 AS grade_bin
FROM ranked_grades) as t
GROUP BY course_id, grade_bin
ORDER BY course_id, grade_bin ASC;
This returns:
course_id grade_bin count
101 90 1
101 100 2
102 85 1
102 90 2
I want to add empty bins to this view, like so:
course_id grade_bin count
101 40 0
101 45 0
... ... ...
101 100 2
102 40 0
... ... ...
So the bins increment by 5, starting at 40, and stopping at 100. My thought was to make a table for empty bins, insert each grade bin and a count of 0, then use UNION but I can't get the UNION to work, since I don't have the course_id column in the empty bin table (nor do I want to - there are a lot and they could change). How would I do a UNION for each group of course_ids?
Assuming that you have created the table bin_table with only 1 column grade_bin and values 40, 45, 50, ...., 100 and also that you have a table cources with the column course_id as primary key, all you have to do is CROSS join these tables and LEFT join to a query that aggregates on the table ranked_grades (no subquery needed):
SELECT c.course_id, b.grade_bin, COALESCE(t.count, 0) AS count
FROM bin_table b CROSS JOIN cources c
LEFT JOIN (
SELECT course_id, FLOOR(grade / 5.00) * 5 AS grade_bin, COUNT(*) AS count
FROM ranked_grades
GROUP BY course_id, grade_bin
) t ON (t.course_id, t.grade_bin) = (c.course_id, b.grade_bin)
Start with a list of bins. Then use cross join to generate all the rows. Finally, join in the existing data.
So:
SELECT c.course_id, gb.grade_bin, COUNT(rg.course_id) as cnt
FROM (SELECT 40 as grade_bin UNION ALL
SELECT 45 as grade_bin UNION ALL
SELECT 50 as grade_bin UNION ALL
. . .
SELECT 100 as grade_bin
) gb CROSS JOIN
(SELECT DISTINCT course_id
FROM ranked_grades
) c LEFT JOIN
ranked_grades rg
ON rg.course_id = c.course_id AND
rg.grade >= gb.grade_bin AND
rg.grade < gb.grade_bin + 5
GROUP BY c.course_id, gb.grade_bin;
Note: This retrieves the courses from the ranked_grades table. You could also use a courses table which is presumably also available.

MYSQL match multiple rows across same table with multiple ids

I'm working out a way to process orders where I would have to satisfy the order from multiple vendors, and I want to know which 2 vendors could supply the given order.
The product_ids in my order are 10,20,30,40,50
SELECT *
FROM vendors
WHERE product_id IN (10,20,30,40,50)
gives me all vendors who have at least 1 of the products
vendor_id | product_id
1234 10
1234 20
1234 30
1234 40
1235 10
1235 40
1236 20
1236 30
1236 40
1237 50
9876 10
9876 20
9876 30
9876 40
9877 10
9877 40
9877 50
9878 10
9878 20
9878 30
9878 50
After several crazy subqueries (too long to post here) I can get to a table that shows me what each vendor is missing, and my thought is to then JOIN to that table based on those missing items and then show the vendors who have the refined list.
result should look like (comma separated or not doesn't matter, just easier to read)
vender_1 | product_id | missing_product_id | vendor_2
1234 10,20,30,40 50 9876,9878
1235 10,40 20,30,50 9878
1236 20,30,40 10,50 9877
1237 50 10,20,30,40 1234
or
vender_1 | product_id | missing_product_id | vendor_2
1234 10
1234 20
1234 30
1234 40
1234 50 9876
1234 50 9878
etc...
You want group by and having:
SELECT v.vendor_id
FROM vendors v -- Shouldn't this be called vendorProducts ?
WHERE v.product_id IN (10, 20, 30, 40, 50)
GROUP BY v.vendor_id
HAVING COUNT(DISTINCT v.product_id) = 5;
For multiple vendors, you can extend the above logic. The idea is to join the table together to get a list of pairs of vendors and all the products that they together have. Then, do the same logic as above:
SELECT v.vendor_id1, v.vendor_id2
FROM (SELECT DISTINCT v1.vendor_id as vendor_id1, v2.vendor_id as vendor_id2,
(CASE WHEN n.n = 1 THEN v1.product_id ELSE v2.product_id END) as product_id
FROM vendors v1 JOIN
vendors v2
ON v1.product_id <> v2.product_id AND
v1.vendor_id < v2.vendor_id CROSS JOIN
(SELECT 1 as n UNION ALL SELECT 2) n
UNION ALL
-- Then include the singletons, just in case
SELECT v.vendor_id, NULL, v.product_id
FROM vendors v
) v
WHERE v.product_id IN (10, 20, 30, 40, 50)
GROUP BY v.vendor_id1, v.vendor_id2
HAVING COUNT(DISTINCT v.product_id) = 5;
You can actually do the product filtering in the subquery -- to make the query more efficient. As for making this more general, the "5" is the number of items. I don't know how the ultimate query is being constructed.
EDIT II:
This is a hard problem with a lot of data. Here is another approach that might work better if you have lots of products and few vendors:
select v1.*, v2.*
from (select vendor_id,
max(product_id = 1) as p1,
max(product_id = 2) as p2,
max(product_id = 3) as p3,
max(product_id = 4) as p4,
max(product_id = 5) as p5,
from vendors
where product_id in (1, 2, 3, 4, 5)
group by vendor_id
) v1 join
(select vendor_id,
max(product_id = 1) as p1,
max(product_id = 2) as p2,
max(product_id = 3) as p3,
max(product_id = 4) as p4,
max(product_id = 5) as p5,
from vendors
where product_id in (1, 2, 3, 4, 5)
group by vendor_id
) v2
on (v1.p1 + v2.p1) > 0 and
(v1.p2 + v2.p2) > 0 and
(v1.p3 + v2.p3) > 0 and
(v1.p4 + v2.p4) > 0 and
(v1.p5 + v2.p5) > 0;
Note: If one vendor has all the products, then it will appear paired with every other vendor.

MYSQL: View statement produces incorrect SUM totals

I have 3 tables. A table for product prices, invoiced products, and ordered products. I am trying to create a view that joins these. I want to output the product prices with a total of invoiced products and a total of ordered products.
products_price
id season_id product_id product_price
1 1 1 3.99
2 1 2 6.99
3 1 3 5.99
4 1 4 5.99
....
invoices_products
id season_id invoice_id product_id piece_qty
1 1 1 1 1600
2 1 2 2 3200
3 1 3 2 200
4 1 4 1 120
....
orders_products
id season_id order_id product_id piece_qty
1 1 1 1 160
2 1 2 1 40
3 1 2 2 20
4 1 3 2 10
....
Here are a few queries from the View statements I've tried so far.
This query gives me everything I want. The View's output is perfect but the SUM() for the first 2 rows is off. total_invoice_product is double for row 1 and 2. total_order_productis 4x for row 1 and 3x for row 2.
Statement 1:
SELECT
`t1`.`id` AS `id`,
`t1`.`season_id` AS `season_id`,
`t1`.`product_id` AS `product_id`,
`t1`.`product_piece_price` AS `product_piece_price`,
SUM(`t2`.`piece_qty`) AS `total_invoice_product`,
SUM(`t3`.`piece_qty`) AS `total_order_product`
FROM
((`products_price` `t1`
LEFT JOIN `invoices_products` `t2` ON (((`t2`.`product_id` = `t1`.`product_id`)
AND (`t2`.`season_id` = `t1`.`season_id`))))
LEFT JOIN `orders_products` `t3` ON (((`t3`.`product_id` = `t1`.`product_id`)
AND (`t3`.`season_id` = `t1`.`season_id`))))
GROUP BY `t1`.`season_id` , `t1`.`product_id`
This query gives me the output that I expect. Its not the full output I want but its correct for the statement. The SUM() totals are off on this one as well.
Statement 2:
SELECT
`t1`.`id` AS `id`,
`t1`.`season_id` AS `season_id`,
`t1`.`product_id` AS `product_id`,
`t1`.`product_price` AS `product_price`,
SUM(`t2`.`piece_qty`) AS `total_invoice_product`,
SUM(`t3`.`piece_qty`) AS `total_order_product`
FROM
((`products_price` `t1`
LEFT JOIN `invoices_products` `t2` ON ((`t2`.`product_id` = `t1`.`product_id`)))
LEFT JOIN `orders_products` `t3` ON ((`t3`.`product_id` = `t1`.`product_id`)))
WHERE
((`t2`.`season_id` = `t1`.`season_id`)
AND (`t2`.`product_id` = `t1`.`product_id`))
GROUP BY `t1`.`season_id` , `t1`.`product_id`
the output I want
id season_id product_id product_price total_invoice total_order
1 1 1 3.99 1720 200
2 1 2 6.99 3400 30
3 1 3 5.99 576
4 1 4 5.99 800
output received for statement 1
id season_id product_id product_price total_invoice total_order
1 1 1 3.99 3440 800
2 1 2 6.99 6800 90
3 1 3 5.99 576
4 1 4 5.99 800
output received for statement 2
id season_id product_id product_price total_invoice total_order
1 1 1 3.99 3440 800
2 1 2 6.99 6800 90
I can build a query like below and it works perfect. I get the exact output I need but this code does not work as a view. I get this error: ERROR 1349: View's SELECT contains a subquery in the FROM clause SQL Statement
Perfect Query but will not work as a view
SELECT
products_price.id,
products_price.season_id,
products_price.product_id,
products_price.product_price,
invoices_grouped.total_invoice_product,
orders_grouped.total_order_product
FROM
products_price
LEFT JOIN
(SELECT
invoices_products.product_id,
invoices_products.season_id,
SUM(invoices_products.piece_qty) AS total_invoice_product
FROM
invoices_products
GROUP BY
invoices_products.product_id) AS invoices_grouped
ON
invoices_grouped.product_id = products_price.product_id
AND
invoices_grouped.season_id = products_price.season_id
LEFT JOIN
(SELECT
orders_products.product_id,
orders_products.season_id,
SUM(orders_products.piece_qty) AS total_order_product
FROM
orders_products
GROUP BY
orders_products.product_id) AS orders_grouped
ON
orders_grouped.product_id = products_price.product_id
AND
orders_grouped.season_id = products_price.season_id
What I need
I've tried several other statements. They either got worse results or the same. Can someone help me get Statement 1 working with a proper SUM?
Edit 1 for a question
The information that this view provides will be called upon a lot. The products_price and invcoices_products tables will not be changed that often. orders_products will be changed a lot. If 2 views are required, would it be more efficient to use the "Perfect" query above or use 2 views?
Edit 2 for another query
Here is another query from my view statement. This query is part of Statement 1 shown above. This query works perfect but it is not complete. I need the second SUM column. When you add the second LEFT JOIN it breaks the SUM totals.
SELECT
`t1`.`id` AS `id`,
`t1`.`season_id` AS `season_id`,
`t1`.`product_id` AS `product_id`,
`t1`.`product_piece_price` AS `product_piece_price`,
SUM(`t2`.`piece_qty`) AS `total_invoice_product`
FROM
(`products_price` `t1`
LEFT JOIN `invoices_products` `t2` ON (((`t2`.`product_id` = `t1`.`product_id`)
AND (`t2`.`season_id` = `t1`.`season_id`))))
GROUP BY `t1`.`season_id` , `t1`.`product_id`
output
id season_id product_id product_price total_invoice
1 1 1 3.99 1720
2 1 2 6.99 3400
3 1 3 5.99 576
4 1 4 5.99 800
Well, MySql has some limitations, so you will need to create 2 views for subqueries and use them:
create view viewInvoices
as
select season_id, product_id, sum(piece_qty) pq
from invoices_products group by season_id, product_id
create view viewOrders
as
select season_id, product_id, sum(piece_qty) pq
from orders_products group by season_id, product_id
select pp.id,
pp.season_id,
pp.product_id,
pp.product_price,
i.pq as total_invoice,
o.pq as total_order
from products_price pp
left join viewInvoices as i
on pp.season_id = i.season_id and pp.product_id = i.product_id
left join viewOrders as o
on pp.season_id = o.season_id and pp.product_id = o.product_id
Try this query
SELECT * from products_price as a left join (select product_id, sum(piece_qty)total_invoices from invoices_products group by product_id) as b on a.product_id=b.product_id left join (select product_id, sum(piece_qty) as total_order from orders_products group by product_id) as c on a.product_id=c.product_id