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