Louie Miranda
Louie Miranda

Reputation: 1159

How to subtract inventory and sale using mysql subquery?

Trying to learn more on sub-query. I am looking for a way to subtract and compare two tables.

  1. Inventory
  2. Sales

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

Answers (7)

Kickstart
Kickstart

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

Adam
Adam

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.

  1. First hypothesis : you have a primary key defined in inventories for (store_id, product_id)

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;
  1. Second hypothesis : just an index, things get more complicated

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

Paul Maxwell
Paul Maxwell

Reputation: 35563

The sample data is quite limited, but I believe we can make these assumptions.

  1. You cannot sell an item unless it has been recorded as inventory
  2. You can have items of inventory you have not sold (yet)

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

shawnt00
shawnt00

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

M Khalid Junaid
M Khalid Junaid

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

BrakNicku
BrakNicku

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

SMA
SMA

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

Related Questions