MYSQL: View statement produces incorrect SUM totals - mysql
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
Related
SQL query correlated which counts of 'where in' parameter
I have a few shops which have products. I want to to write a query where I put product_id and I get max(id)` offer from this shop. And very important thing - all products must be in each shop. If I want two products and the shop has only one, it isn't correct. My query what is wrong now: select max(o.offer_id) as max_offer_id, o.product_id, shop_id, prod.name from offer as o join product as prod on prod.product_id = o.product_id where prod.product_id in ( 1,2 ) group by o.product_id, shop_id , prod.name order by shop_ida I would like to get a result like this: max_offer_id product_id shop_id name 10 1 1 PROD1 9 2 1 PROD2 11 1 2 PROD1 12 2 2 PROD2 but I get this: max_offer_id product_id shop_id name 10 1 1 PROD1 9 2 1 PROD2 11 1 2 PROD1 12 2 2 PROD2 1 1 3 PROD1 <-- wrong 4 1 5 PROD1 <-- wrong 6 2 6 PROD2 <-- wrong tables and some data: https://www.db-fiddle.com/f/vyMmeUqmgYHmMirMFzwx2Y/5
This will need selecting records that match ALL in the list. One such example is here. using same query can be modified as - select max(o.offer_id) as max_offer_id, o.product_id, t.shop_id, prod.name from offer as o join product as prod on prod.product_id = o.product_id join (select shop_id, count(*) from offer where product_id in ( 1,2 ) group by shop_id having count(distinct product_id)>= (select count(*) from product where product_id in ( 1,2 )) ) t on o.shop_id = t.shop_id where prod.product_id in (1,2) group by o.product_id, shop_id , prod.name Refer fiddle here.
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!
How do I get the MAX date of foreign table in MySQL and return only the row with that data without mixing records?
I have a few MySQL tables from which I need to JOIN and return data. The return data must show only one row for one of the JOINed tables, but MySQL mixes the rows. I have tried different methods using subqueries and normal JOIN with GROUP but the results remain pretty much the same. Example table structure suppliers id name ... 1 ACME ... 2 EMCA ... 3 ORG ...` ratings id supplier_id rating expiry_date report_file 1 1 5.0 2017-01-31 a.pdf 3 1 7.9 2019-06-30 c.pdf 4 2 5.0 2016-01-31 d.pdf 5 2 2.0 2018-11-30 g.pdf 6 245 9.5 2009-03-31 p.pdf spends id report_id supplier_id amount 1 1 1 150.00 2 1 2 100.00 3 1 245 200.00 Here are example queries I have tried to resolve this and return the correct dataset with no luck. SELECT reports.id, suppliers.id AS supplier_id, suppliers.name, ... spends.amount, ... ratings.rating, ratings.report_file, ratings.expiry_date FROM reports INNER JOIN spends ON reports.id=spends.report_id INNER JOIN suppliers ON spends.supplier_id=suppliers.id LEFT JOIN ( SELECT id, level, report_file, supplier_id, MAX(expiry_date) AS expiry_date FROM ratings GROUP BY supplier_id ) ratings ON (ratings.supplier_id=suppliers.id AND ratings.expiry_date >= reports.period_start) ... WHERE reports.id = 1 GROUP BY spends.id ORDER BY spends.amount DESC Another query SELECT reports.id, suppliers.id AS supplier_id, suppliers.name, ... spends.amount, ... ratings.rating, ratings.report_file, MAX(ratings.expiry_date) AS expiry_date FROM reports INNER JOIN spends ON reports.id=spends.report_id INNER JOIN suppliers ON spends.supplier_id=suppliers.id LEFT JOIN ratings ON (ratings.supplier_id=suppliers.id AND ratings.expiry_date >= reports.period_start) ... WHERE reports.id = 1 GROUP BY spends.id ORDER BY spends.amount DESC I expect the results to be id supplier_id name amount rating report_file expiry_date 1 1 ACME 150.00 7.9 c.pdf 2019-06-30 1 2 EMCA 100.00 2.0 g.pf 2018-11-30 1 245 MACE 200.00 null null``` However, the actual output is ```sql id supplier_id name amount rating report_file expiry_date 1 1 ACME 150.00 5.0 a.pdf 2019-06-30 1 2 EMCA 100.00 5.0 d.pf 2018-11-30 1 245 MACE 200.00 null null Please could anyone advise how I can fix this.
try a query like this: SELECT reports.id, suppliers.id AS supplier_id, suppliers.name, ... spends.amount, ... r.rating, r.report_file, r.expiry_date FROM reports INNER JOIN spends ON reports.id=spends.report_id INNER JOIN suppliers ON spends.supplier_id=suppliers.id LEFT JOIN ratings r ON r.supplier_id=suppliers.id AND r.`expire_date` = ( SELECT MAX(`expire_date`) FROM ratings WHERE supplier_id = supplier.id GROUP BY supplier_id) ) as maxdate ... WHERE reports.id = 1 GROUP BY spends.id ORDER BY spends.amount DESC
SUM, GROUP BY AND LEFT JOIN in query
I've two tables: `orders` order_id order_office_id order_invoice_id 1 1 1 2 2 2 3 2 2 4 2 3 5 1 4 `invoices` inv_id inv_order_id inv_amount 1 1 500.00 2 0 320.00 3 3 740.00 4 4 160.00 With this query: SELECT SUM(inv_amount) matrah, order_office_id FROM `invoices` LEFT JOIN orders ON order_invoice_id = inv_id OR inv_order_id = order_id WHERE order_id IS NOT NULL GROUP BY order_office_id It is multiplying some amounts. What I want to get sum of amounts by office: office_id sum 1 660.00 2 1060.00 How to get proper amounts by single query?
This does what you want, I think select sum(inv_amount) as matrah, (select order_office_id from orders where order_invoice_id = inv_id limit 1) as office from invoices group by office; I've removed the OR, because you were getting two office ids for one of the orders, so it was ambiguous. I've included a subquery to make sure that only one office is applied to each order.
This query may help you out: SELECT SUM(i.inv_amount) matrah, o.order_office_id FROM `invoices` AS i LEFT JOIN `orders` AS o ON o.order_invoice_id = i.inv_id GROUP BY order_office_id OR statement in your ON condition caused the problem.
MySQL join next lower value from other table
I've the two tables orders id article amount 1 1 1 2 2 50 and prices id article min_amount price 1 1 1 42.99 2 2 1 5.06 3 2 5 4.55 4 2 10 4.3 5 2 25 4.05 6 2 100 2.66 The prices tables contains IDs of articles and a minimum amount you would have to buy to get a bulk discount (which would change the price for the order). I would like to join prices into orders, so that the result looks like: id article amount price 1 1 1 42.99 2 2 50 4.05 The order id 2 is above the minimum (25) to get the article for 4.05€, but still below 100 at which you would get a bigger discount, so the query would to have pick the next-lower value. I've tried this query so far SELECT orders.id AS id, orders.article, orders.amount, prices.price, (orders.amount - prices.min_amount) AS discount_diff FROM orders LEFT JOIN prices ON (prices.article = orders.article) AND (prices.min_amount <= orders.amount) which gives this result id article amount price discount_diff 1 1 1 42.99 0 2 2 50 5.06 49 2 2 50 4.55 45 2 2 50 4.3 40 2 2 50 4.05 25 You can find this example on "js"fiddle: http://sqlfiddle.com/#!9/1b2bf/8
The query you need is this: SELECT orders.id AS id, orders.article, orders.amount, prices.price FROM orders INNER JOIN prices ON ( prices.article = orders.article and prices.min_amount <= orders.amount) INNER JOIN ( SELECT orders.article, orders.amount, min(prices.price) minprince FROM orders INNER JOIN prices ON (prices.article = orders.article AND prices.min_amount <= orders.amount) GROUP BY orders.article, orders.amount) b ON ( prices.article = b.article AND orders.amount = b.amount AND prices.price = b.minprince) See it here: http://sqlfiddle.com/#!9/1b2bf/27