Optimize query with multiple subquery joins - mysql

I have a query that gets the product description from product table (1st select) then subtracts it to the following subquery statements to get the no. of stocks remaining:
-sum of each product bought in the inventory table (2nd)
-sum of each product sold in the sales_detail table (3rd)
-sum of each product transferred to another branch in the stock_transfer table (4th)
-sum of each product that got damaged in the damaged_product table (5th)
The problem is every time this query loads, it goes full search of all 4 tables to get the sum of the quantity columns. And as time goes by, more records are stored and the query will become slower. Any suggestions?
SELECT p.Id,p.Product_Name Product,p.Description, c.Category_Name Category,sc.Subcategory_Name Subcategory,s.Supplier_Name Supplier, p.Selling_Price `Unit Price`,i.Stocks,s.Sales, i.Stocks - IFNULL(s.Sales, 0) - IFNULL(t.Transfer, 0) - IFNULL(d.Damage, 0) AS Remaining
FROM (SELECT Id, Product_Name, Description, Selling_Price, Category_Id, Subcategory_Id, Supplier_Id FROM product WHERE enable_flag = 1) p
LEFT OUTER JOIN(SELECT product_id, COALESCE(SUM(quantity), 0) AS Stocks
FROM inventory
WHERE enable_flag = 1 GROUP BY product_id) i ON p.Id = i.product_id
LEFT OUTER JOIN(SELECT product_id, COALESCE(SUM(quantity), 0) AS Sales
FROM sales_detail
WHERE enable_flag = 1 GROUP BY product_id) s USING(product_id)
LEFT OUTER JOIN(SELECT product_id, COALESCE(SUM(transfer_quantity), 0) AS Transfer
FROM stock_transfer
WHERE enable_flag = 1 GROUP BY product_id) t USING(product_id)
LEFT OUTER JOIN(SELECT product_id, COALESCE(SUM(damaged_quantity), 0) AS Damage
FROM damaged_product
WHERE enable_flag = 1 GROUP BY product_id) d USING(product_id)
JOIN Category c ON p.Category_Id=c.Id
JOIN Subcategory sc ON p.Subcategory_Id=sc.Id
JOIN Supplier s ON p.Supplier_Id=s.Id;

The use of subqueries prevents the use of indexes, which can slow down a query. I would suggest a query such as this:
SELECT p.*, sc.Subcategory_Name as Subcategory, s.Supplier_Name as Supplier,
p.Selling_Price as `Unit Price`,
(SELECT COALESCE(SUM(quantity), 0)
FROM inventory i
WHERE s.enable_flag = 1 AND s.product_id = p.product_id
) as stocks,
(SELECT COALESCE(SUM(quantity), 0)
FROM sales_detail sd
WHERE i.enable_flag = 1 AND i.product_id = p.product_id
) as sales,
(SELECT COALESCE(SUM(tansferquantity), 0)
FROM stock_transfer st
WHERE st.enable_flag = 1 AND st.product_id = p.product_id
) as transfers,
(SELECT COALESCE(SUM(damaged_quantity), 0)
FROM damage d
WHERE d.enable_flag = 1 AND d.product_id = p.product_id
) as damaged
FROM product p JOIN
Category c
ON p.Category_Id = c.Id JOIN
Subcategory sc
ON p.Subcategory_Id = sc.Id JOIN
Supplier s
ON p.Supplier_Id = s.Id
WHERE p.enable_flag = 1;
For performance, the underlying tables want indexes on (product_id, enable_flag, quantity). MySQL can use the index for aggregation in a correlated subquery.
I realize that you also have calculated values. You may need to do these calculations in an outer query, taking the hit of an additional materialization of the subquery.

Related

MySQL: Ranking from multiple tables, sub queries?

This is a MySQL question. I have three tables with the following columns:
transactions (table): transact_id, customer_id, transact_amt, product_id,
products (table): product_id, product_cost, product_name, product_category
customers (table): customer_id, joined_at, last_login_at, state, name, email
I'd like a query that finds out the most popular item in every state and the state. One of the tricky parts is that some product_name have multiple product_id. Therefore I though joining the three tables that generate an output with two columns: state and product_name. Until here that worked fine doing this:
SELECT p.product_name, c.state
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
This selects all the products, and the states from where the customer is. The problem is that I can't find the way to rank the mos popular product per state. I tried different group by, order by and using subqueries without success. I suspect I need to do subqueries, but I can't find the way to resolve it. The expected outcome should look like this:
most_popular_product | state
Bamboo | WA
Walnut | MO
Any help will be greatly appreciated.
Thank you!
You need a subquery that gets the count of transactions for each product in each state.
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
Then write another query that has this as a subquery, and gets the highest count for each state.
SELECT state, MAX(count) AS maxcount
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t
GROUP BY state
Finally, join them together:
SELECT t1.product_name AS most_popular_product, t1.state
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t1
JOIN (
SELECT state, MAX(count) AS maxcount
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t
GROUP BY state
) AS t2 ON t1.state = t2.state AND t1.count = t2.maxcount
This is basically the same pattern as SQL select only rows with max value on a column, just using the first grouped query as the table you're trying to group.

select sum qty from 2 tables

In MySQL I have 4 tables:
- product(id)
- order(id)
- order_detail_1(id, product_id, order_id, qty)
- order_detail_2(id, product_id, order_id, qty)
I want to get the sum of the quantity of products sold from the 2 tables (order_detail_1, order_detail_2) grouping them by product
produt can existe in order_detail_1 and not in order_detail_2 and vice versa
i tested this query and it worked but I want a simpler query without the union and the subquery.
select tmp.product_id ,sum(tmp.qty) from
(
(
select order_detail_1.product_id ,sum(order_detail_1.qty)
from order_detail_1
inner join order on order_detail_1.id_order = order.id
where order_detail_1.product_id is not null
group by order_detail_1.product_id
)
union all
(
select order_detail_2.product_id ,sum(order_detail_2.qty)
from order_detail_2
inner join order on order_detail_2.id_order = order.id
where order_detail_2.product_id is not null
group by order_detail_2.product_id
)
) tmp
group by tmp.product_id
It looks like you're not using order table other then checking if it exists, so you can use EXISTS()
SELECT p.product_id,sum(p.qty) as qty
FROM (SELECT product_id,qty,id_order FROM order_detail_1
WHERE product_id IS NOT NULL
UNION ALL
SELECT product_id,qty,id_order FROM order_detail_2
WHERE product_id IS NOT NULL) p
WHERE EXISTS(SELECT 1 FROM order o
WHERE o.id = p.id_order)
GROUP BY p.product_id
If a product is in only one table, you can use left join:
select p.id, (coalesce(sum(od1.qty), 0) + coalesce(sum(od2.qty, 0))) as qty
from product p left join
order_detail_1 od1
on od1.product_id = p.id left join
order_detail_2 od2
on od2.product_id = p.id
group by p.id;
This formulation depends on the fact that the two tables are exclusion -- a product is in only one table.
EDIT:
If products can exist in both tables, then you need to aggregate them first:
select p.id, (coalesce(od1.qty, 0) + coalesce(od2.qty, 0)) as qty
from product p left join
(select product_id, sum(qty) as qty
from order_detail_1 od1
group by product_id
) od1
on od1.product_id = p.id left join
(select product_id, sum(qty) as qty
from order_detail_2 od2
group by product_id
) od2
on od2.product_id = p.id;

LEFT JOIN returns NULL if there are just one column in table

I try to get the latest created product price. Every Product is unique but can have different prices. However, my query only works if a product have more than a price as row in the product_price table:
This is my query:
SELECT
i.name AS title,
i.id AS product_id,
m.name AS manufacturer,
image,
price_sales,
price_new,
price_used,
price_old
FROM product_info as i
LEFT JOIN product_manufacturer AS m ON i.manufacturer_id = m.id
LEFT JOIN (SELECT * FROM product_price ORDER BY created_at DESC LIMIT 1) AS p ON i.id = p.id_product
WHERE category_id = 2
AND i.is_deactivated IS NULL
LIMIT 0, 20;
I just need the latest created price row.
Result
The problem you have is that the subquery:
(SELECT * FROM product_price ORDER BY created_at DESC LIMIT 1)
Does not get the latest price per product, but simply the latest price, so will only ever return one row, meaning only one of your products will actually have a price.
The way to resolve this is to remove any prices where a newer one exists, so for simplicity if you look just at the price table, the following will give you only the latest product prices:
SELECT p.*
FROM product_price AS p
WHERE NOT EXISTS
( SELECT 1
FROM product_price AS p2
WHERE p2.id_product = p.id_product
AND p2.created_at > p.created_at
);
However, MySQL will optmise LEFT JOIN/IS NULL better than NOT EXISTS (although I think the former conveys intention better), so a more efficient approach would be:
SELECT p.*
FROM product_price AS p
LEFT JOIN product_price AS p2
ON p2.id_product = p.id_product
AND p2.created_at > p.created_at
WHERE p2.id IS NULL;
Finally, introducing this back to your main query, you would end up with:
SELECT i.name AS title,
i.id AS product_id,
m.name AS manufacturer,
i.image,
p.price_sales,
p.price_new,
p.price_used,
p.price_old
FROM product_info as i
LEFT JOIN product_manufacturer AS m
ON m.id = i.manufacturer_id
LEFT JOIN product_price AS p
ON p.id_product = i.id
LEFT JOIN product_price AS p2
ON p2.id_product = p.id_product
AND p2.created_at > p.created_at
WHERE i.category_id = 2
AND i.is_deactivated IS NULL
AND p2.id IS NULL
LIMIT 0, 20;

MYSQL NOT IN with multiple column names

i have 2 mysql queries,
1 is to select the all branch stocks and other is to select stock count lists.
i need to select all the branch stocks which is not included in stock count lists( by considering stock barcode and stock batch)
my first query to select all branch stock is
SELECT SQL_CALC_FOUND_ROWS stock_id ,product_name , stock_batch,stock_barcode
FROM stock INNER JOIN product ON product_id = stock_product
LEFT JOIN packing ON product_package=packing_id
WHERE stock_branch = 2 AND stock_available_flg = 1
group by stock_id
and second query to select counted stock lists is
SELECT SQL_CALC_FOUND_ROWS stock_id,stock_barcode, product_name,stock_batch FROM stock_count_item
INNER JOIN stock ON stock_id = stock_count_item_stock
INNER JOIN product ON stock_product = product_id
INNER JOIN category ON category_id = product_category
WHERE stock_count_item_stock_count = 42 GROUP BY stock_barcode
what i need is to select all branch stock which is not included in stock count lsts with same barcode and batch
my query is
SELECT SQL_CALC_FOUND_ROWS stock_id ,product_name , stock_batch,stock_barcode
FROM stock
INNER JOIN product ON product_id = stock_product
LEFT JOIN packing ON product_package=packing_id
WHERE stock_branch = 2 AND stock_available_flg = 1 AND ( stock_batch,stock_barcode)
not in
(SELECT stock_batch ,stock_barcode FROM stock_count_item
INNER JOIN stock ON stock_id = stock_count_item_stock
INNER JOIN product ON stock_product = product_id
INNER JOIN category ON category_id = product_category WHERE stock_count_item_stock_count = 42)
but this not gives the correct result .How am consider stock_batch,stock_barcode together to correct this?
Use NOT EXISTS instead of NOT IN
BUT because you have not identified which tables each column comes from so I (or anyone maintaining your code) cannot learn a column's source from reading the query. ALWAYS prefix the column by the table (or table's alias).
SELECT
SQL_CALC_FOUND_ROWS stock_id
, product_name
, stock_batch
, stock_barcode
FROM stock
INNER JOIN product ON product_id = stock_product
LEFT JOIN packing ON product_package = packing_id
WHERE stock_branch = 2
AND stock_available_flg = 1
NOT NOT EXSTS (
SELECT 1
FROM stock_count_item
INNER JOIN stock ON stock_id = stock_count_item_stock
INNER JOIN product ON stock_product = product_id
INNER JOIN category ON category_id = product_category
WHERE stock_count_item_stock_count = 42
and outer_alias.stock_batch = inner_alias.stock_batch
and outer_alias.stock_barcode = inner_alias.stock_barcode
)
;
I have tried to indicate what is required by using
and outer_alias.stock_batch = inner_alias.stock_batch
the "outer_alias" is an alias (or tablename) from before the NOT EXISTS subquery, and the inner_alias is the reverse. At least one of these has to be an alias if you choose to use a tablename for one of hem.
e.g.
select ... from stock where not exists (... from stock s and stock.id = s.id)
"stock" is the "outer_alias" of stock.id
"s" is the "inner_alias" of s.id
the final query with aliases (via comment below)
SELECT
SQL_CALC_FOUND_ROWS stock_id
, product_name
, s.stock_batch --<< please please please use aliases everywhere
, s.stock_barcode
FROM stock s
INNER JOIN product ON product_id = stock_product
LEFT JOIN packing ON product_package = packing_id
WHERE stock_branch = 2
AND stock_available_flg = 1
AND NOT EXISTS (
SELECT
1
FROM stock_count_item
INNER JOIN stock st ON stock_id = stock_count_item_stock
INNER JOIN product ON stock_product = product_id
INNER JOIN category ON category_id = product_category
WHERE stock_count_item_stock_count = 42
AND s.stock_batch = st.stock_batch
AND s.stock_barcode = st.stock_barcode
)

Select top sales products

I have three tables like this
orders(id, status, ...)
products(id, created_at, ...)
product_order(order_id, product_id, quantity)
I want to select the most sold products first then continue with latest products taking the quantity in consideration, Here's my try
SELECT products.* FROM products
LEFT JOIN product_order ON product_order.product_id = products.id
LEFT JOIN orders ON orders.id = product_order.order_id
WHERE orders.status != 'REJECTED'
GROUP BY product_order.product_id
ORDER BY COUNT(*) DESC, products.created_at
This statement returns the products that are not sold first because I am using left join and they count more than the sold ones.. also I don't know how to take the quantity in consideration
Thank you,
This should work :
SELECT p.*, sum(po.quantity) qty
FROM products p
LEFT OUTER JOIN product_order po ON po.product_id = p.id
LEFT OUTER JOIN orders o ON o.id = po.order_id
WHERE o.status != 'REJECTED'
GROUP BY po.product_id
ORDER BY qty DESC, p.created_at
If you want the most sold products you could add
AND products.quantity = SELECT max(quantity) from products
after your WHERE statement