How to value cost of sales using FIFO in SQL Server - sql

I want value the cost of goods sold using the FIFO method.
I know how many beers I sold. Based on my price I bought those beers at, what is the cost of those sales? So, my sales of 7 Peronis are valued at £1.70 -- based on the FIFO valuation method.
How do I calculate in SQL Server.
I am going to be working this out for many products and from many branches at the same time, so I would like to use a method that does not involve cursors (or any other types of loops).
-- SETUP
DROP TABLE IF EXISTS #Deliveries;
CREATE TABLE #Deliveries (DeliveryDate DATE, ProductCode VARCHAR(10), Quantity INT, Cost DECIMAL(6,2));
INSERT INTO #Deliveries (DeliveryDate, ProductCode, Quantity, Cost)
VALUES
('2020-11-23', 'PERONI', 2, 0.20), ('2020-11-24', 'PERONI', 4, 0.30), ('2020-11-25', 'PERONI', 7, 0.10),
('2020-11-23', 'BUDWEISER', 5, 0.20), ('2020-11-24', 'BUDWEISER', 5, 0.50), ('2020-11-25', 'BUDWEISER', 4, 0.80);
DROP TABLE IF EXISTS #StockResults;
CREATE TABLE #StockResults (ProductCode VARCHAR(10), SalesQty INT, CostOfSalesValue DECIMAL(6,2));
INSERT INTO #StockResults (ProductCode, SalesQty)
VALUES ('PERONI', 7), ('BUDWEISER', 4);
SELECT * FROM #Deliveries;
SELECT * FROM #StockResults;
-- DESIRED RESULT
/*
ProductCode SalesQty CostOfSalesValue
PERONI 7 1.70
BUDWEISER 4 0.80
*/

This is probably not very efficient but it shows you one way in which this can be achieved which should help you come up with your finished solution. I would imagine that there needs to be a lot more complexity built into this process to account for things like stock wastage, but I'll leave that up to you:
Query
-- SETUP
declare #Deliveries table (DeliveryDate date, ProductCode varchar(10), Quantity int, Cost decimal(6,2));
insert into #Deliveries (DeliveryDate, ProductCode, Quantity, Cost) values ('2020-11-23', 'PERONI', 2, 0.20), ('2020-11-24', 'PERONI', 4, 0.30), ('2020-11-25', 'PERONI', 7, 0.10),('2020-11-23', 'BUDWEISER', 5, 0.20), ('2020-11-24', 'BUDWEISER', 5, 0.50), ('2020-11-25', 'BUDWEISER', 4, 0.80);
declare #StockResults table (ProductCode varchar(10), SalesQty int);
insert into #StockResults (ProductCode, SalesQty) values ('PERONI', 7), ('BUDWEISER', 4);
-- QUERY
with r as
(
select d.ProductCode
,d.DeliveryDate
,d.Quantity
,d.Cost
,isnull(sum(d.Quantity) over (partition by d.ProductCode order by d.DeliveryDate rows between unbounded preceding and 1 preceding),0) as RunningQuantityStart
,sum(d.Quantity) over (partition by d.ProductCode order by d.DeliveryDate) as RunningQuantityEnd
from #Deliveries as d
)
select r.ProductCode
,s.SalesQty
,sum(case when r.RunningQuantityEnd >= s.SalesQty
then (s.SalesQty - r.RunningQuantityStart) * r.Cost
else (r.RunningQuantityEnd - r.RunningQuantityStart) * r.Cost
end
) as CostOfSalesValue
from r
join #StockResults as s
on r.ProductCode = s.ProductCode
and r.RunningQuantityStart < s.SalesQty
group by r.ProductCode
,s.SalesQty;
##Output
+-------------+----------+------------------+
| ProductCode | SalesQty | CostOfSalesValue |
+-------------+----------+------------------+
| BUDWEISER | 4 | 0.80 |
| PERONI | 7 | 1.70 |
+-------------+----------+------------------+

Below query might help you:
declare #maxQty int
select #maxQty = max(SalesQty) from #StockResults
;WITH AllNumbers AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number+1 FROM AllNumbers WHERE Number < #maxQty
)
select ProductCode, SalesQty, SUM(Cost) as CostOfSalesValue from
(
SELECT SR.ProductCode, SR.SalesQty, DLM.RN, DLM.Cost FROM #StockResults SR
outer apply
(
select ROW_NUMBER()OVER (order by DeliveryDate asc) as RN, Dl.Cost from #Deliveries Dl
inner join AllNumbers AL on AL.Number <= Dl.Quantity
where Dl.ProductCode = SR.ProductCode
) as DLM
) result
where RN <= SalesQty
group by ProductCode, SalesQty

Related

Retrieve specific value if set, or general if not

I have to write a query which will import some data from table. Table structure is like:
Item_ID
Plant
Price
I have second table with
Item_ID
Plant
Second table is a key, and I have to match rows from first table to get valid price. Seems to be easy, however:
In first table column plant might determinate specific plant, or have value 'ALL'. What I want to do is retrieve price for given plant, if it is set, or get price for value 'ALL' if there is no row for given plant. In other words:
If first.Plant = second.Plant
return price
Else If first.Plant = 'ALL'
return price
Else
return NULL
I can't use simple ON first.Plant = second.Plant OR first.Plant = 'ALL', because there might be two rows: one for given plant and second for rest with value 'ALL'. I need to return only first price in that case. E.g.
Item_ID | Plant | Price
2 | M1 | 10,0
2 | All | 12,0
1 | All | 9,0
In that case for Item_ID = 2 and Plant = M1 the only valid price = 10, but for Item_ID = 2 and Plant = M2 price = 12, and for any Item_ID = 1 price = 9
I hope You understood something after my explanation ;)
Using ROW_Number and a Common Table Expression you can ensure that ROW_NUMBER is partitioned by Item_ID and Plant and ordered such that if there are two rows then 'All' is second. You then simple select the rows with a row number = 1:
Setup
CREATE TABLE #Price
(
Item_ID int,
Plant Varchar(20),
Price Decimal(6,2)
)
CREATE TABLE #Plant
(
Item_ID int,
Plant VARCHAR(20)
)
INSERT INTO #Price
VALUES (2, 'M1', 10.0),
(2, 'All', 12.0),
(1, 'All', 9.0)
INSERT INTO #Plant
VALUES (2, 'M1'),
(2, 'M2'),
(1, 'M1'),
(1, 'M2')
Here's the query for SQL Server >= 2012
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY IIF(PR.Plant = 'All', 1, 0)) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1
And here's a version using CASE which will work for all SQL Server >= 2008 R2
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY CASE WHEN PR.Plant = 'All' Then 1 Else 0 End) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1

Product Final Price after Many Discount given

I have two tables.
One table of Ids and their prices, and second table of discounts per Id.
In the table of discounts an Id can has many Discounts, and I need to know the final price of an Id.
What is the Best way to query it (in one query) ?
The query should be generic for many discounts per id (not only 2 as mentioned below in the example)
For example
Table one
id price
1 2.00
2 2.00
3 2.00
Table two
id Discount
1 0.20
1 0.30
2 0.40
3 0.50
3 0.60
Final result:
id OrigPrice PriceAfterDiscount
1 2.00 1.12
2 2.00 1.20
3 2.00 0.40
Here's another way to do it:
SELECT T1.ID, T1.Price, T1.Price * EXP(SUM(LOG(1 - T2.Discount)))
FROM T1 INNER JOIN T2 ON T1.ID = T2.ID
GROUP BY T1.ID, T1.Price
The EXP/LOG trick is just another way to do multiplication.
If you have entries in T1 without discounts in T2, you could change the INNER JOIN to a LEFT JOIN. You would end up with the following:
ID Price Discount
4 2.00 NULL
Your logic can either account for the null in the discounted price column and take the original price instead, or just add a 0 discount record for those.
Generally it can be done with a trick with LOG/EXP functions but it is complex.
Here is a basic example:
declare #p table(id int, price money)
declare #d table(id int, discount money)
insert into #p values
(1, 2),
(2, 2),
(3, 2)
insert into #d values
(1, 0.2),
(1, 0.3),
(2, 0.4),
(3, 0.5),
(3, 0.6)
select p.id,
p.price,
p.price * ca.discount as PriceAfterDiscount
from #p p
cross apply (select EXP(SUM(LOG(1 - discount))) as discount FROM #d where id = p.id) ca
For simpler(cursor based approach) you will need a recursive CTE, but in this case you need some unique ordering column in Discounts table to run it correctly. This is shown in #Tanner`s answer.
And finally you can approach this with a regular cursor
I believe this produces the desired results using a CTE to iterate through the discounts. The solution below is re-runnable in isolation.
Edited: to include data that might not have any discounts applied in the output with a left join in the first part of the CTE.
CREATE TABLE #price
(
id INT,
price DECIMAL(5, 2)
);
CREATE TABLE #discount
(
id INT,
discount DECIMAL(5, 2)
);
INSERT INTO #price
(
id,
price
)
VALUES
(1, 2.00),
(2, 2.00),
(3, 2.00),
(4, 3.50); -- no discount on this item
INSERT INTO #discount
(
id,
discount
)
VALUES
(1, 0.20),
(1, 0.30),
(2, 0.40),
(3, 0.50),
(3, 0.60);
-- new temporary table to add a row number to discounts so we can iterate through them
SELECT d.id,
d.discount,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY d.discount) rn
INTO #GroupedDiscount
FROM #discount AS d;
-- note left join in first part of cte to get prices that aren't discounted included
WITH cte
AS (SELECT p.id,
p.price,
CASE
WHEN gd.discount IS NULL THEN
p.price
ELSE
CAST(p.price * (1.0 - gd.discount) AS DECIMAL(5, 2))
END AS discountedPrice,
gd.rn
FROM #price AS p
LEFT JOIN #GroupedDiscount AS gd
ON gd.id = p.id
AND gd.rn = 1
UNION ALL
SELECT cte.id,
cte.price,
CAST(cte.discountedPrice * (1.0 - gd.discount) AS DECIMAL(5, 2)) AS discountedPrice,
cte.rn + 1 AS rn
FROM cte
INNER JOIN #GroupedDiscount AS gd
ON gd.id = cte.id
AND gd.rn = cte.rn + 1
)
SELECT cte.id,
cte.price,
MIN(cte.discountedPrice) AS discountedPrice
FROM cte
GROUP BY id,
cte.price;
DROP TABLE #price;
DROP TABLE #discount;
DROP TABLE #GroupedDiscount;
Results:
id price discountedPrice
1 2.00 1.12
2 2.00 1.20
3 2.00 0.40
4 3.50 3.50 -- no discount
As others have said, EXP(SUM(LOG())) is the way to do the calculation. Here is basically same approach from yet another angle:
WITH CTE_Discount AS
(
SELECT Id, EXP(SUM(LOG(1-Discount))) as TotalDiscount
FROM TableTwo
GROUP BY id
)
SELECT t1.id, CAST(Price * COALESCE(TotalDiscount,1) AS Decimal(18,2)) as FinalPRice
FROM TableOne t1
LEFT JOIN CTE_Discount d ON t1.id = d.id
SQLFIddle Demo

Count days between dates in two rows based on condition

I'm writing query which has to select few infos. Below table:
ID ID-Toner Quantity Location Order_date Send_date
1 2 1 55 20.01.2015 26.01.2015
2 2 1 41 22.02.2015 26.02.2015
3 2 1 35 23.02.2015 26.02.2015
4 5 1 77 25.02.2015 25.02.2015
5 2 1 55 25.02.2015 26.02.2015
I need to select all columns and additional column with number of days between two dates: Order_date and previous Order_date for location = ie.: 55.
Sample result should look like:
ID ID-Toner Quantity Location Order_date Send_date Number_of_days
1 2 1 55 20.01.2015 26.01.2015 0
5 2 1 55 25.02.2015 26.02.2015 36
How to select such a query?
updated after clarifications in the PO
let'say that it needs to do a sort of aggregation on data called ranking, that is a type of classification based on numbering in succession order tbale's rows.
In our case the order is given by the Orders dates.
This is a quite cross-dbms solution (date fields are suppased to be Datetime type and DATEDIFF is a function of MySql) so I think that you can adapt to your dbms quite easily.
You can try the sql on Sql Fiddle at http://sqlfiddle.com/#!9/290e9
Table
CREATE TABLE Orders
(`ID` int, `IDToner` int, `Quantity` int, `Location` int, `Order_date` Date, `Send_date` Date)
;
INSERT INTO Orders
(`ID`, `IDToner`, `Quantity`, `Location`, `Order_date`, `Send_date`)
VALUES
(1, 2, 1, 55, STR_TO_DATE('20.01.2015','%d.%m.%Y'), STR_TO_DATE('26.01.2015','%d.%m.%Y')),
(2, 2, 1, 41, STR_TO_DATE('22.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y')),
(3, 2, 1, 35, STR_TO_DATE('23.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y')),
(4, 5, 1, 77, STR_TO_DATE('25.02.2015','%d.%m.%Y'), STR_TO_DATE('25.02.2015','%d.%m.%Y')),
(5, 5, 1, 77, STR_TO_DATE('25.04.2015','%d.%m.%Y'), STR_TO_DATE('25.04.2015','%d.%m.%Y')),
(6, 5, 1, 77, STR_TO_DATE('25.06.2015','%d.%m.%Y'), STR_TO_DATE('25.06.2015','%d.%m.%Y')),
(7, 5, 1, 77, STR_TO_DATE('25.08.2015','%d.%m.%Y'), STR_TO_DATE('25.08.2015','%d.%m.%Y')),
(8, 2, 1, 55, STR_TO_DATE('25.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y'))
;
Query
SELECT
ID,
ID_Toner,
Quantity,
Location,
Order_date,
Send_date,
days_from_previous_order
FROM(
SELECT
current_ID AS ID,
current_IDToner AS ID_Toner,
current_Quantity AS Quantity,
current_Location AS Location,
current_Send_Date AS Send_date,
current_Order_Date AS Order_date,
previous_Order_Date,
COALESCE(DATEDIFF(current_Order_Date, previous_Order_Date),0) AS days_from_previous_order
FROM(
SELECT
TabOrdersRanking_currents.ID AS current_ID,
TabOrdersRanking_currents.IDToner AS current_IDToner,
TabOrdersRanking_currents.Quantity AS current_Quantity,
TabOrdersRanking_currents.Location AS current_Location,
TabOrdersRanking_currents.Send_Date AS current_Send_Date,
TabOrdersRanking_currents.Order_Date AS current_Order_Date,
TabOrdersRanking_previous.Order_Date AS previous_Order_Date
FROM(
SELECT Orders.*, #rank1 := #rank1 + 1 rank
FROM Orders
,(Select #rank1 := 0) r1
order by location, order_date
) TabOrdersRanking_currents
LEFT JOIN(
SELECT Orders.*, #rank2 := #rank2 + 1 rank
FROM Orders
,(Select #rank2 := 0) r2
order by location, order_date
) TabOrdersRanking_previous
on TabOrdersRanking_currents.Location = TabOrdersRanking_previous.Location
and TabOrdersRanking_currents.rank - TabOrdersRanking_previous.rank = 1
) TabOrdersSuccessionRanking
) TabWithDaysFromPrevious;

Getting the daily sales report given the date

Given a date, the table displays the items sold on that date.
The table groups the category of the items and show the total sales value for each category. At the end, the report shows the total sales value for the day(s). Something like this:
ID Category Price Units Total Value
----------------------------------------------------
2244 class 10.50 10 105.00
2555 class 5.00 5 25.00
3455 class 20.00 1 20.00
Total 16 150.00
1255 pop 20.00 5 100.00
5666 pop 10.00 10 100.00
Total 15 200,00
1244 rock 2.50 20 50.00
8844 rock 5.00 50 250.00
Total 70 300.00
----------------------------------------------
Total Daily Sales 101 650.00
DBMS: SQL Server 2012
Bolded: primary keys
Item (upc, title*, type, category, company, year, price, stock)
PurchaseItem (receiptId, upc, quantity)
Order (receiptId, date, cid, card#, expiryDate, expectedDate, deliveredDate)
Rough work of what I have so far..
SELECT I.upc, I.category, I.price, P.quantity, P.quantity*I.price AS totalValue, SUM(totalValue), SUM(P.quantity) AS totalUnits, O.date
FROM Item I, Order O
JOIN (SELECT P.quantity
FROM PurchaseItem P, Item I
WHERE I.upc = P.upc)
ON I.upc = P.upc
WHERE O.date = ({$date}) AND O.receiptId = P.receiptId
GROUP BY I.upc, I.category, I.price, P.quantity, totalValue, O.date
Alright, this isn't right and I'm kind of stuck. Need some help!
I want it so it produces the total value of items from one category then in the end, it will add up the total value of the items from all categories.
SAMPLE TABLES
Item(2568, Beatles, rock, Music Inc, 1998, 50.50, 5000)
PurchaseItem (5300, 2568, 2)
Order (5300, 10/09/2014, ...Not important..) cid is customerId and card# is credit card number.
SSRS or a similar reporting package can usually handles this for you natively. If this has to be done in SQl script then a quick solution would be a cursor/while loop. pseudo code would look like this;
create tempsales table;
get distinct list of categories;
for each category
Begin
insert into tempsale (...)
sales where category = #category
group by category, item
insert into tempsales (...) -- use NULL value for item or perhaps a value of 'TOTAL'
Sales where category = #category
group by category
when last category
insert into tempsales (...) -- use NULL value for item AND Category or perhaps a value of 'TOTAL'
total with no group
end
select from tempsales;
See if this helps:
CREATE SAMPLE DATA
use tempdb;
create table Item(
upc int,
category varchar(100),
price decimal(8,2)
)
create table PurchaseItem(
receiptId int,
upc int,
quantity int
)
create table [Order](
receiptId int,
[date] date
)
insert into Item values
(2244, 'class', 10.50),
(2555, 'class', 5.0),
(3455, 'class', 20.0),
(1255, 'pop', 20.0),
(5666, 'pop', 10.0),
(1244, 'rock', 2.50),
(8844, 'rock', 5.0)
insert into PurchaseItem values
(5300, 2244, 10),
(5300, 2555, 5),
(5300, 3455, 1),
(5300, 1255, 5),
(5300, 5666, 10),
(5300, 1244, 20),
(5300, 8844, 50)
insert into [Order] values(5300,'20140910')
SOLUTION
;with cte as(
select
i.upc as Id,
i.category as Category,
i.price as Price,
p.quantity as Units,
price * quantity as TotalValue
from [Order] o
inner join PurchaseItem p
on p.receiptId = o.receiptId
inner join Item i
on i.upc = p.upc
)
select
Id,
case
when grouping(Id) = 1 then 'Total'
else Category
end as Category,
Price,
sum(Units) as Units,
sum(TotalValue) as TotalValue
from cte
group by
grouping sets(Category, (Category, Id, Price, Units, TotalValue))
union all
select
null,
'Total Daily Sales',
null,
sum(Units),
sum(Totalvalue)
from cte
DROP SAMPLE DATA
drop table item
drop table PurchaseItem
drop table [Order]

Database Join Query

I am having a problem with a database join query and I am looking for someone to help me out.
Basically I've got two tables, Invoices and Receipts.
Invoices
Invoice ID
Amount
Date_Added
Receipts
ReceiptID
InvoiceID
Amount
Date_Added
The thing is that I need to produce a table like below, but I have multiple records in Receipts and I am pretty sure that the data is stored in a good way, just not exactly sure what the query would be.
InvoiceID RecieptID Amount Balance Date_Added
1 0 100.00 100.00 01.05.2012
1 1 100.00 0.00 02.05.2012
2 0 250.00 250.00 03.05.2012
3 0 100.00 350.00 04.05.2012
2 2 100.00 250.00 05.05.2012
Does this make sense? So it should be in date order. So effectively I can see line by line what is going on each date.
Setup:
USE tempdb;
GO
CREATE TABLE dbo.Invoices
(
InvoiceID INT,
Amount DECIMAL(12,2),
DateAdded SMALLDATETIME
);
CREATE TABLE dbo.Receipts
(
ReceiptID INT,
InvoiceID INT,
Amount DECIMAL(12,2),
DateAdded SMALLDATETIME
);
SET NOCOUNT ON;
INSERT dbo.Invoices SELECT 1, 100, '20120501'
UNION ALL SELECT 2, 250, '20120503'
UNION ALL SELECT 3, 100, '20120504';
INSERT dbo.Receipts SELECT 1, 1, 100, '20120502'
UNION ALL SELECT 2, 2, 100, '20120505';
Query:
;WITH x AS
(
SELECT InvoiceID, ReceiptID, Amount, DateAdded,
rn = ROW_NUMBER() OVER (ORDER BY DateAdded)
FROM
(
SELECT InvoiceID, ReceiptID = 0, Amount, DateAdded
FROM dbo.Invoices -- where clause?
UNION ALL
SELECT InvoiceID, ReceiptID, Amount, DateAdded
FROM dbo.Receipts -- where clause?
) AS y
),
z AS
(
SELECT xrn = x.rn, x.InvoiceID, x.ReceiptID, x.Amount, x.DateAdded,
PlusMinus = CASE WHEN x.ReceiptID > 0 THEN -x.Amount ELSE x.Amount END
FROM x LEFT OUTER JOIN x AS x2
ON x.rn = x2.rn + 1
)
SELECT InvoiceID, ReceiptID, Balance = (
SELECT SUM(COALESCE(PlusMinus, Amount))
FROM z AS z2
WHERE z2.xrn <= z.xrn
), Amount, DateAdded
FROM z
ORDER BY DateAdded;
Cleanup:
DROP TABLE dbo.Invoices, dbo.Receipts;
you can make a view that select data from the 2 tables like if the DB is oracle :
CREATE VIEW INVOICE_RECPT_VIEW as
select a.InvoiceID, b.RecieptID, b.Amount, b.Date_Added
from invoices a, receipts b
where a.InvoiceID = b.InvoiceID
order by Date_Added