简体   繁体   中英

SQL - Calculate cumulative average cost price for a product

I was wondering if anyone could at least point me in the right direction with this issue.

I need to calculate the average cost of inventory but the caveat being that it should only be calculated on the latest procurement of products, relating to what we have in stock.

To try and explain in a better fashion, product x, we have purchased in total 1,200 units from various suppliers at different costs. We only have 150 units left in stock so, to get the average purchase price and value of the remaining items, we would need to get the average cost of the products from the latest 150 units purchased only .

I have a view which produces the following:

 ProductId (int) - PK
 ProductName (varchar(150))
 StockInHand (int)

The related tables are as follows (truncated for ease of use)

PurchaseOrder

PurchaseOrderId (int) - PK
PurchaseOrderDate (DateTime)
PurchaseOrderStatus (int) - FK

Purchase order status - 1 = Open, 2 = Checked In

PurchaseOrderLine

PurchaseOrderLineId (int) - PK 
Qty (int)
UnitPrice (decimal(18,2))
ProductId (int) - FK
PurchaseOrderId (int) - FK

UPDATE

I have created an sql-fiddle here:

http://sqlfiddle.com/#!18/69288/1

You will see that I have three purchase orders of goods:

1: Qty 20 @ £40ea
2: Qty 20 @ £30ea
3: Qty 10 @ £25ea

I have two orders of the same goods:

1: Qty 10
2: Qty 15

From this, I can get the stock on hand: 25

I need the average purchase price of the stock I have left.

I have 25 units left so I need to work backwards from the latest purchase orders to get the value.

I will take 10 from purchase order 3 and 15 from purchase order 2:

10 * £25 = £250
15 * £30 = £450

£700 / 25 = £28

I hope this makes my problem clearer!

Thank you

UPDATE 2

Thank you very much, Sticky Bit for taking the time out to post a solution to my problem and for explaining it so thoughtfully.

I have tried this on my dev DB and seem to have a problem.

I have 4 purchase orders for the same product (with two placed on the same day)

I have not worked out how to format tables gracefully in this text editor so please bear with me:

PurchaseOrderId / PurchaseOrderDate
2 / 2018-07-28
3 / 2018-07-29
4 / 2018-07-30
5 / 2018-07-30

I have the following PurchaseOrderLine

PurchaseOrderLineId / PurchaseOrderId / ProductId / Qty / UnitPrice
3 / 2 / 8 / 20 / 400.00
4 / 3 / 8 / 40 / 420.00
5 / 4 / 8 / 25 / 500.00
6 / 5 / 8 / 1 / 200.00

Running the following:

SELECT pol.productid,
   po.purchaseorderdate,
   sum(pol.qty) qty,
   avg(pol.unitprice) unitprice
   FROM purchaseorder po
        INNER JOIN purchaseorderline pol
                   ON pol.purchaseorderid = po.purchaseorderid
   GROUP BY pol.productid,
            po.purchaseorderdate;

Gives me these results:

8 2018-07-28 00:00:00.000 20 400.000000
8 2018-07-29 00:00:00.000 40 420.000000
8 2018-07-30 00:00:00.000 26 350.000000

You will note that the average cost for the products purchased on 30th July is off (it is taking the average between the two prices and not taking the qty into account - I'm not sure if this is by design?)

If I then run the following:

SELECT po.productid,
   po.purchaseorderdate,
   sum(po.qty) OVER (PARTITION BY po.productid
                     ORDER BY po.purchaseorderdate) qty,
   sum(po.unitprice) OVER (PARTITION BY po.productid
                           ORDER BY po.purchaseorderdate) unitprice
   FROM (SELECT pol.productid,
                po.purchaseorderdate,
                sum(pol.qty) qty,
                avg(pol.unitprice) unitprice
                FROM purchaseorder po
                     INNER JOIN purchaseorderline pol
                                ON pol.purchaseorderid = po.purchaseorderid
                GROUP BY pol.productid,
                         po.purchaseorderdate) po;

I get the following results:

8 2018-07-28 00:00:00.000 20 400.000000
8 2018-07-29 00:00:00.000 60 820.000000
8 2018-07-30 00:00:00.000 86 1170.000000

Again, something seems to be amiss here with regards to the unitprice.

Any help greatly appreciated!

Kind regards

I don't see 'UnitCost,' so I'm going to use 'UnitPrice' as the cost in my code example.

I don't know that this is perfect, but it may help you in the right direction.

SELECT (SUM(UnitPrice)/150) FROM OrderDetails
WHERE PurchaseOrderId IS BETWEEN 1050 AND 1200
GO
  1. First we need to find a query to give us the current stock level for each product. For that, we left join purchaseorderline and orderline to product and calculate the differences for each row. Since a product can be in multiple orders we additionally aggregate the result, to get the overall difference -- the current stock level -- for each product.

     SELECT p.productid, p.productname, sum(coalesce(pol.qty, 0) - coalesce(ol.qty, 0)) qty FROM product p LEFT JOIN purchaseorderline pol ON pol.productid = p.productid LEFT JOIN orderline ol ON ol.productid = p.productid GROUP BY p.productid, p.productname; 
  2. Next we need the quantity, that was stocked for each product and day (of purchaseorders ). To get that, we inner join purchaseorder and purchaseorderline . Again we aggregate to account for the possible case, that multiple orders on the same day were made for the same product.

     SELECT pol.productid, po.purchaseorderdate, sum(pol.qty) qty, sum(pol.qty * pol.unitprice) unitprice FROM purchaseorder po INNER JOIN purchaseorderline pol ON pol.purchaseorderid = po.purchaseorderid GROUP BY pol.productid, po.purchaseorderdate; 

    We can now use the previous result and window functions to get the sum of the quantity stocked and the average price of the products up to each day.

     SELECT po.productid, po.purchaseorderdate, sum(po.qty) OVER (PARTITION BY po.productid ORDER BY po.purchaseorderdate) qty, sum(po.unitprice) OVER (PARTITION BY po.productid ORDER BY po.purchaseorderdate) / sum(po.qty) OVER (PARTITION BY po.productid ORDER BY po.purchaseorderdate) unitprice FROM (SELECT pol.productid, po.purchaseorderdate, sum(pol.qty) qty, sum(pol.qty * pol.unitprice) unitprice FROM purchaseorder po INNER JOIN purchaseorderline pol ON pol.purchaseorderid = po.purchaseorderid GROUP BY pol.productid, po.purchaseorderdate) po; 

Now we put the results from 1. and 2. together using OUTER APPLY . For each product, we select the TOP 1 result from 2. ordered by the day descending -- ie younger orders first --, that stocked a quantity greater than or equal to the one currently in stock.

SELECT p.productid,
       p.productname,
       po.unitprice
       FROM (SELECT p.productid,
                    p.productname,
                    sum(coalesce(pol.qty, 0) - coalesce(ol.qty, 0)) qty
                    FROM product p
                         LEFT JOIN purchaseorderline pol
                                   ON pol.productid = p.productid
                         LEFT JOIN orderline ol
                                   ON ol.productid = p.productid
                    GROUP BY p.productid,
                             p.productname) p
            OUTER APPLY (SELECT TOP 1
                                po.unitprice
                                FROM (SELECT po.productid,
                                             po.purchaseorderdate,
                                             sum(po.qty) OVER (PARTITION BY po.productid
                                                               ORDER BY po.purchaseorderdate) qty,
                                             sum(po.unitprice) OVER (PARTITION BY po.productid
                                                                     ORDER BY po.purchaseorderdate)
                                             /
                                             sum(po.qty) OVER (PARTITION BY po.productid
                                                               ORDER BY po.purchaseorderdate) unitprice
                                             FROM (SELECT pol.productid,
                                                          po.purchaseorderdate,
                                                          sum(pol.qty) qty,
                                                          sum(pol.qty * pol.unitprice) unitprice
                                                          FROM purchaseorder po
                                                               INNER JOIN purchaseorderline pol
                                                                          ON pol.purchaseorderid = po.purchaseorderid
                                                          GROUP BY pol.productid,
                                                                   po.purchaseorderdate) po) po
                                WHERE po.productid = p.productid
                                      AND po.qty >= p.qty
                                ORDER BY po.purchaseorderdate DESC) po;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM