Reputation: 1159
Trying to learn more on sub-query. I am looking for a way to subtract and compare two tables.
My data records are as follows:
Inventory:
mysql> select store_id, product_id, sum(quantity) as inventory from inventories where store_id = 1 group by product_id;
+----------+------------+-----------+
| store_id | product_id | inventory |
+----------+------------+-----------+
| 1 | 8 | 24 |
| 1 | 10 | 4 |
| 1 | 14 | 24 |
+----------+------------+-----------+
3 rows in set (0.00 sec)
Sales
mysql> select store_id, product_id, sum(quantity) as sales from sales where store_id = 1 group by product_id;
+----------+------------+-------+
| store_id | product_id | sales |
+----------+------------+-------+
| 1 | 8 | 12 |
| 1 | 14 | 2 |
| 1 | 8 | 1 |
+----------+------------+-------+
2 rows in set (0.00 sec)
What is the proper sub-query to have the following result?
+----------+------------+-----------+-------+-----------+
| store_id | product_id | inventory | sales | remaining |
+----------+------------+-----------+-------+-----------+
| 1 | 8 | 24 | 12 | 12 |
| 1 | 14 | 24 | 2 | 22 |
| 1 | 8 | 12 | 1 | 11 |
+----------+------------+-----------+-------+-----------+
Upvotes: 4
Views: 6065
Reputation: 21513
Assuming you have a table of products and a table of stores, then join those together, to get the combination of products and stores. This way a row isn't dependent on a product still being on inventory on a store you are interested in, or the product to have yet been sold from a store you are interested in.
Then LEFT OUTER JOIN sub queries to get the inventory and sales or each product / store.
SELECT b.store_id, a.product_id, IFNULL(c.inventory, 0), IFNULL(d.sales, 0) , (IFNULL(c.inventory, 0) - IFNULL(d.sales, 0)) AS remaining
FROM products a
INNER JOIN stores b
ON b.store_id = 1
LEFT OUTER JOIN
(
SELECT store_id, product_id, SUM(quantity) AS inventory
FROM inventories
WHERE store_id = 1
GROUP BY store_id, product_id
) c
ON a.product_id = c.product_id
AND b.store_id = c.store_id
LEFT OUTER JOIN
(
SELECT store_id, product_id, SUM(quantity) AS sales
FROM sales
WHERE store_id = 1
GROUP BY store_id, product_id
) d
ON a.product_id = d.product_id
AND b.store_id = d.store_id
Upvotes: 1
Reputation: 18807
The ideal subquery will be the subquery which will use the keys of your table.
For your inventory table, you should have a PRIMARY KEY on (store_id, product_id).
ALTER TABLE inventories ADD PRIMARY KEY (store_id, product_id).
If this is not intended to be unique (in which case, it is not really an inventory, but an "incoming item" table), you can set an INDEX for those two columns.
ALTER TABLE inventories ADD INDEX (store_id, product_id).
You have to define the same index in the sales table
ALTER TABLE sales ADD INDEX (store_id, product_id).
After defining those keys, we can know set the perfect and long performance subquery.
sql:
SELECT t1.store_id, t1.product_id, t1.quantity inventory,
IFNULL(sum(t2.quantity), 0) sales,
t1.quantity-IFNULL(sum(t2.quantity), 0) remaining
FROM inventories t1
LEFT JOIN sales t2 ON t1.store_id=t2.store_id
AND t1.product_id=t2.product_id
WHERE t1.store_id=1
GROUP BY t1.store_id, t1.product_id;
You do the same thing except that instead of querying inventories
table, you will replace it with the following subquery:
SELECT store_id, product_id, SUM(quantity) quantity
FROM inventory
GROUP BY store_id, product_id;
The result will be the following:
SELECT t1.store_id, t1.product_id, t1.quantity inventory,
IFNULL(sum(t2.quantity), 0) sales,
t1.quantity-IFNULL(sum(t2.quantity), 0) remaining
FROM
(SELECT store_id, product_id, SUM(quantity) quantity
FROM inventory
GROUP BY store_id, product_id) t1
LEFT JOIN sales t2 ON t1.store_id=t2.store_id
AND t1.product_id=t2.product_id
WHERE t1.store_id=1
GROUP BY t1.store_id, t1.product_id;
Upvotes: 1
Reputation: 35563
The sample data is quite limited, but I believe we can make these assumptions.
If is not necessary to aggregate inventory then you could use a single "derived table", which is a type of subquery, like this:
SELECT
I.store_id
, I.product_id
, COALESCE(I.inventory, 0) AS INVENTORY
, COALESCE(S.sales, 0) AS SALES
, COALESCE(I.inventory, 0) - COALESCE(S.sales, 0) AS REMAINING
FROM Inventory I
LEFT JOIN (
SELECT
store_id
, product_id
, SUM(sales) AS SALES
FROM Sales
WHERE store_id = 1
GROUP BY
product_id
) S ON I.store_id = S.store_id
AND I.product_id = S.product_id
WHERE I.store_id = 1
ORDER BY
I.store_id
, I.product_id
;
If it is necessary to also aggregate inventory you could use 2 "derived tables" like this:
SELECT
I.store_id
, I.product_id
, COALESCE(I.inventory, 0) AS INVENTORY
, COALESCE(S.sales, 0) AS SALES
, COALESCE(I.inventory, 0) - COALESCE(S.sales, 0) AS REMAINING
FROM (
SELECT
store_id
, product_id
, SUM(inventory) AS INVENTORY
FROM Inventory
WHERE store_id = 1
GROUP BY
product_id
) I
LEFT JOIN (
SELECT
store_id
, product_id
, SUM(sales) AS SALES
FROM Sales
WHERE store_id = 1
GROUP BY
product_id
) S ON I.store_id = S.store_id
AND I.product_id = S.product_id
ORDER BY
I.store_id
, I.product_id
;
Or you may use "correlated subqueries" within the select clause like this:
SELECT
I.store_id
, I.product_id
, COALESCE((
SELECT
SUM(sales)
FROM sales S
WHERE S.store_id = I.store_id
AND S.product_id = I.product_id
), 0) AS SALES
, I.inventory - COALESCE((
SELECT
SUM(sales)
FROM sales S
WHERE S.store_id = I.store_id
AND S.product_id = I.product_id
), 0) AS REMAINING
FROM Inventory AS I
WHERE I.store_id = 1
ORDER BY
I.store_id
, I.product_id
;
You have asked which is the "proper sub-query". I believe all the above are correct syntactically, but "proper" I don't fully understand. If you mean which will perform best I would suggest derived tables in preference to correlated subqueries, but to arrive at an answer that applies to all situations is near impossible.
To evaluate best performance requires better definition of the data, tables and indexes, then use of execution plans is strongly recommended. It may also be the case that NOT using any subqueries is the best approach (i.e. joins).
see the queries above as a demonstration here: http://sqlfiddle.com/#!9/fa6b6/1
Upvotes: 1
Reputation: 17915
I posted an answer and then re-read what you wanted. I realized that I had mis-read something and I see that you do want sales to be treated as individual transactions that are deducted in sequence i.e. "history". You are still going to need some kind of transaction id or transaction date to determine which order they are to be applied in. That was already noted in another answer too. Perhaps you want to rely on a MySQL row id or something. I don't know enough about MySQL to tell help you with that.
select
i.store_id, i.product_id,
i.inventory - s.previous_sales as inventory,
s.quantity as sales,
i.inventory - s.previous_sales - s.quantity as remaining
from
inventories as i inner join
(
select store_id, product_id, quantity,
(
select sum(quantity)
from sales as s2
where
s2.store_id = s.store_id and s2.product_id = s.product_id
/* all sales for this store and product prior to this one */
and s2.[sequencing column] < s.[sequencing column]
) as previous_sales
from sales
group by store_id, product_id
) as s
on s.store_id = i.store_id and s.product_id = i.product_id
where
i.store_id = 1
I don't know how you're going to handle changes in your inventory or how far back to look in sales that need to be deducted from inventory. This addresses the problem as you wrote it up.
Upvotes: 1
Reputation: 64466
You can use results from your queries and join them to calculate the remaining quantity for each product
SELECT
a.store_id,
a.product_id,
a.inventory,
b.sales,
a.inventory - b.sales AS remaining
FROM (
SELECT store_id, product_id, COALESCE(SUM(quantity),0) AS inventory
FROM inventories WHERE store_id = 1
GROUP BY product_id) a
LEFT JOIN (
SELECT store_id, product_id, COALESCE(SUM(quantity),0) AS sales
FROM sales WHERE store_id = 1
GROUP BY product_id ) b USING(store_id, product_id)
Upvotes: 4
Reputation: 5991
To achieve the desired output you need to calculate running totals of product sales. To get meaningful data, the data in sales
table must be ordered chronologically. So you need at least one more field to sort data - it doesn't matter if it's a timestamp, or id
field. Let's assume there is an id
field in sales table. This is a query to get what you described:
SELECT
sales.id,
sales.store_id,
sales.product_id,
inventories.quantity-IFNULL(SUM(sales_2.quantity), 0) as inventory,
sales.quantity as sales,
inventories.quantity-IFNULL(SUM(sales_2.quantity), 0) - sales.quantity as remaining
FROM
sales
INNER JOIN
inventories ON inventories.store_id = sales.store_id
AND inventories.product_id = sales.product_id
LEFT JOIN
sales AS sales_2 ON sales_2.store_id = sales.store_id
AND sales_2.product_id = sales.product_id
AND sales_2.id < sales.id
GROUP BY sales.id , sales.store_id , sales.product_id
ORDER BY sales.id
The second instance of sales
table called sales_2
is used to calculate the sum of earlier sales (sales_2.id<sales.id
)
You can exclude sales.id
from the select
clause, but you need to keep it in group by
and order by
.
Upvotes: 3
Reputation: 37023
Try joining the two tables as :
SELECT I.store_id, I.product_id, I.inventory, S.sales, (I.inventory - S.sales) AS remaining
FROM Sales S INNER JOIN INVENTOR I
ON I.store_id = S.store_id
AND I.product_id = S.product_id
Upvotes: 2