sql query for fifo inventory - sql

I'm having trouble with a simple problem for fifo sql query (to calculate profit for each sales day).
There are two tables Production and Invoice. For each day of sales, I have to output total sales profit by using FIFO method.
Example, for second day profit , I have to use leftover item from previous day with their respecting price.
Here is the tables and desired output result
CREATE TABLE Production
(
id int identity(1,1) primary key,
Productid varchar(10),
pdate date,
Qty int,
Price decimal(18, 2),
);
INSERT INTO Production (Productid,pDate, Qty ,Price) VALUES ('PD1', '01/01/2017', 8, 200);
INSERT INTO Production (Productid,pDate ,Qty ,Price) VALUES ('PD2', '02/01/2017', 14, 300);
INSERT INTO Production (Productid,pDate ,Qty ,Price) VALUES ('PD3', '03/01/2017', 15, 150);
CREATE TABLE Sales
(
id int identity(1,1) primary key,
Sid varchar(10),
sDate date,
Productid varchar(10),
Qty int,
);
INSERT INTO Sales (Sid,sDate ,Productid ,Qty) VALUES ('S001', '04/01/2017', 'PD1', 5);
INSERT INTO Sales (Sid,sDate ,Productid ,Qty) VALUES ('S002', '05/01/2019', 'PD2', 4);
INSERT INTO Sales (Sid,sDate ,Productid ,Qty) VALUES ('S003', '06/01/2019', 'PD3', 6);
Manual calculation for leftover formula for each day
( existing - sales qty ) + purchase qty = leftover

I think a simple check on sales.qty < purchase.qty won't work. Since even if you have sales.qty < purchase.qty but have leftovers from last day then you will be using those leftovers first.
You should use try this:
with cte as(
select s.id,s.Sid,sDate,s.Productid,s.qty AS Qty,s.qty as saleqty,p.qty as productqty,p.price
,sum(p.qty-s.qty) over (order by sdate) as leftover
from purchase P
inner join sales S
on p.productid=s.productid
and p.pdate=s.sdate
)
select id, Sid,sDate,Productid,Qty,
case when lag(leftover) over (order by sdate)>0 then lag(leftover *price) over( order by sdate)
+( saleqty-lag(leftover) over (order by sdate)) * price
else saleqty * price end as profit
from cte;

Hope this would help.
SELECT
s.sid,
s.sdate,
p.productid,
s.qty,
CASE
WHEN s.qty <= p.qty
THEN s.qty*p.price
ELSE p.qty*p.price + (s.qty-p.qty) * (SELECT price FROM purchase WHERE pdate IN (SELECT MAX(pdate) FROM purchase WHERE pdate < s.sdate))
END AS PROFIT
FROM purchase p
JOIN sales s
ON p.productid = s.productid
AND p.pdate = s.sdate

Related

SQL query to derive average sale of an item

Below are two tables. An item is being sold at different values during different periods. I need to calculate average sale of the item. I need to come to an optimal working query. I think the trick would be to map each sales date with their start_date and end_date and multiply the quatity with their respective price for that period and then derive the avergae using the formula total sales amount/ total quatity.
CREATE TABLE sales(
item_id varchar(255) ,
start_date date ,
end_date date,
price int
) ;
insert into sales values
('mobile','2021-01-01','2021-01-05',500),
('mobile','2021-01-06','2021-01-09',400),
('mobile','2021-01-10','2021-01-15',900) ;
CREATE TABLE item(
sales_id int,
item_id varchar(255) ,
sales_date date ,
quatity int
) ;
insert into item values
(101,'mobile','2021-01-01',10),
(102,'mobile','2021-01-06',20),
(103,'mobile','2021-01-15',30) ;
select item_id, sum(total_amount)/sum(quatity) avg_price from
(
select s.item_id, s.price, i.quatity, s.price * i.quatity as total_amount
from sales s, item i where i.item_id = s.item_id
and i.sales_date between s.start_date and s.end_date
) as A
group by item_id;
Please try this,
SELECT
i.item_id,avg(s.price*i.quatity) FROM
item I
LEFT JOIN
sales S
on
I.item_id=S.item_id
AND I.sales_date >=S.start_date AND I.sales_date<=S.end_date
group by i.item_id;

Sum last two records including last record of a group

In SQL Server 2017, how do I sum the last two records and show the last record in a single query?
CREATE TABLE Billing
(
Customer CHAR(12),
Month INT,
Amount INT
)
GO
INSERT INTO Billing VALUES ('AAAA', 3, 5)
INSERT INTO Billing VALUES ('AAAA', 2, 0)
INSERT INTO Billing VALUES ('AAAA', 1, 2)
INSERT INTO Billing VALUES ('BBBB', 10, 0)
INSERT INTO Billing VALUES ('BBBB', 12, 1)
INSERT INTO Billing VALUES ('BBBB', 11, 0)
INSERT INTO Billing VALUES ('BBBB', 13, 6)
Expected output:
Customer Total Last 2 Bills Last Bill
-----------------------------------------
AAAA 5 5
BBBB 7 6
I tried using SUM with LAST_VALUE with ORDER BY
You can filter out rows by using the ROW_NUMBER() window function, as in:
select
customer,
sum(amount) as total_last_2_bills,
sum(case when rn = 1 then amount else 0 end) as last_bill
from (
select
*,
row_number() over (partition by customer order by month desc) as rn
from billing
) x
where rn <= 2
group by customer
See SQL Fiddle.
You can use window functions:
select customer, (prev_amount + amount), amount
from (select b.*,
lag(amount) over (partition by customer order by month) as prev_amount,
lead(month) over (partition by customer order by month) as next_month
from billing b
) b
where next_month is null;
If you want to ignore values of 0, then filter:
select customer, (coalesce(prev_amount, 0) + amount), amount
from (select b.*,
lag(amount) over (partition by customer order by month) as prev_amount,
lead(month) over (partition by customer order by month) as next_month
from billing b
where amount <> 0
) b
where next_month is null;

MAX of SUMs from GROUP BY of JOIN

I'm stuck.
I have two tables:
First, [PurchasedItemsByCustomer] with the columns:
[CustID] INT NULL,
[ItemId] INT NULL,
[Quantity] INT NULL,
[OnDate] DATE NULL
Second, Table [Items] with the columns:
[ItemId] INT NULL,
[Price] FLOAT NULL,
[CategoryId] INT NULL
I need to output a list with 3 columns:
a month
the category which sold the most (in items quantity) in that month
how many items from that category were purchased in that month.
Thank you
I think you can use a query like this:
;With SoldPerMonth as (
select datepart(month, p.onDate) [Month], i.CategoryId [Category], sum(p.Quntity) [Count]
from PurchasedItemsByCustomer p
join Items i on p.ItemId = i.ItemId
group by datepart(month, p.onDate), i.CategoryId
), SoldPerMonthRanked as (
select *, rank() over (partition by [Month] order by [Count] desc) rnk
from SoldPerMonth
)
select [Month], [Category], [Count]
from SoldPerMonthRanked
where rnk = 1;
SQL Server Demo
Note: In above query by using rank() will provide all max categories if you want to return just one row use row_number() instead.
Divide et Impera:
with dept_sales as(
select month(ondate) as month, year(ondate) as year, category, count(*) as N -- measure sales for each month and category
from purchase join items using itemid
group by year(ondate), month(ondate), category)
select top 1 * --pick the highest
from dept_sales
where year = year(current_timestamp) -- I imagine you need data only for current year
order by N desc --order by N asc if you want the least selling category
If you don't group by year you'll get january of all the years in the same 'january' entry, so I added a filter on current year.
I used CTE for code clarity to split the phases of calculation, you can nest them if you want to.
Here you go,
SELECT
A.[CategoryId],
A.[Month],
A.[CategoryMonthCount]
FROM
(
SELECT
A.[CategoryId],
A.[Month],
A.[CategoryMonthCount],
RANK() OVER(
PARTITION BY A.[Month]
ORDER BY A.[CategoryMonthCount] DESC) [RN]
FROM
(
SELECT
I.[CategoryId],
MONTH(PIBC.[OnDate]) [Month],
SUM(PIBC.[Quantity]) [CategoryMonthCount]
FROM
[dbo].[PurchasedItemsByCustomer] PIBC
JOIN
[dbo].[Items] I
GROUP BY
I.[CategoryId],
MONTH(PIBC.[OnDate])
) A
) A
WHERE
A.[RN] = 1;

Any one help me to solve this i try my best but did not solve this?

ItemName Price CreatedDateTime
New Card 50.00 2014-05-26 19:17:09.987
Recharge 110.00 2014-05-26 19:17:12.427
Promo 90.00 2014-05-27 16:17:12.427
Membership 70.00 2014-05-27 16:17:12.427
New Card 50.00 2014-05-26 19:20:09.987
Out Put : Need a query which Sum the sale of Current hour and
sale of item which have maximum sale in that hour in breakdownofSale
Column.
Hour SaleAmount BreakDownOfSale
19 210 Recharge
16 160 Promo
This should do it
create table #t
(
ItemName varchar(50),
Price decimal(18,2),
CreatedDateTime datetime
);
set dateformat ymd;
insert into #t values('New Card', 50.00, '2014-05-26 19:17:09.987');
insert into #t values('Recharge', 110.00, '2014-05-26 19:17:12.427');
insert into #t values('Promo', 90.00, '2014-05-27 16:17:12.427');
insert into #t values('Membership', 70.00, '2014-05-27 16:17:12.427');
insert into #t values('New Card', 50.00, '2014-05-26 19:20:09.987');
with cte as
(
select datepart(hh, CreatedDateTime) as [Hour],
ItemName,
Price,
sum(Price) over (partition by datepart(hh, CreatedDateTime)) SaleAmount,
ROW_NUMBER() over (partition by datepart(hh, CreatedDateTime) order by Price desc) rn
from #t
)
select Hour,
SaleAmount,
ItemName
from cte
where rn = 1
Though i am not clear with the question, based on your desired output, you may use the query as below.
SELECT DATEPART(HOUR,CreatedDateTime) AS Hour, sum(Price) AS Price, ItemName AS BreakDownOfSale from TableName WHERE BY ItemName,DATEPART(HOUR,CreatedDateTime)
Replace table name and column name with the actual one.
Hope this helps!
Here is the sample query.
You can use SQL Server Windows functions to get the result you need.
DECLARE #Table TABLE
(
ItemName NVARCHAR(40),
Price DECIMAL(10,2),
CreatedDatetime DATETIME
)
-- Fill table.
INSERT INTO #Table
( ItemName, Price, CreatedDatetime )
VALUES
( N'New Card' , 50.00 , '2014-05-26 19:17:09.987' ),
( N'Recharge' , 110.00 , '2014-05-26 19:17:12.427' ) ,
( N'Promo' , 90.00 , '2014-05-27 16:17:12.427' ) ,
( N'Membership' , 70.00 , '2014-05-27 16:17:12.427' ) ,
( N'New Card' , 50.00 , '2014-05-26 19:20:09.987' )
-- Check record(s).
SELECT * FROM #Table
-- Get record(s) in required way.
;WITH T1 AS
(
SELECT
DATEPART(HOUR, T.CreatedDatetime) AS Hour,
CONVERT(DATE, T.CreatedDatetime) AS Date,
T.ItemName AS BreakDownOfSales,
-- Date and hour both will give unique record(s)
SUM(Price) OVER (PARTITION BY CONVERT(DATE, T.CreatedDatetime), DATEPART(HOUR, CreatedDateTime)) AS SaleAmount,
ROW_NUMBER() OVER(PARTITION BY CONVERT(DATE, T.CreatedDatetime), DATEPART(HOUR, T.CreatedDatetime) ORDER BY T.Price DESC) AS RN
FROM
#Table T
)
SELECT
T1.Date ,
T1.Hour ,
T1.SaleAmount,
T1.BreakDownOfSales
FROM
T1
WHERE T1. RN = 1
ORDER BY
T1.Hour
Check this simple solution, Please convert it to SQL Server Query.
This will give you perfect result even if you have multiple date data.
SELECT HOUR(CreatedDateTime), SUM(Price),
(SELECT itemname FROM t it WHERE HOUR(ot.CreatedDateTime) = HOUR(it.CreatedDateTime) AND
DATE(ot.CreatedDateTime) = DATE(it.CreatedDateTime)
GROUP BY itemname
ORDER BY price DESC
LIMIT 1
) g
FROM t ot
GROUP BY HOUR(CreatedDateTime);

Summing historic cost rates over booked time (single effective date)

We have a time management system where our employees or contractors (resources) enter the hours they have worked, and we derive a cost for it. I have a table with the historic costs:
CREATE TABLE ResourceTimeTypeCost (
ResourceCode VARCHAR(32),
TimeTypeCode VARCHAR(32),
EffectiveDate DATETIME,
CostRate DECIMAL(12,2)
)
So I have one date field which marks the effective date. If we have a record which is
('ResourceA', 'Normal', '2012-04-30', 40.00)
and I add a record which is
('ResourceA', 'Normal', '2012-05-04', 50.00)
So all hours entered between the 30th April and the 3rd of May will be at £40.00, all time after midnight on the 4th will be at £50.00. I understand this in principle but how do you write a query expressing this logic?
Assuming my time table looks like the below
CREATE TABLE TimeEntered (
ResourceCode VARCHAR(32),
TimeTypeCode VARCHAR(32),
ProjectCode VARCHAR(32),
ActivityCode VARCHAR(32),
TimeEnteredDate DATETIME,
HoursWorked DECIMAL(12,2)
)
If I insert the following records into the TimeEntered table
('ResourceA','Normal','Project1','Management1','2012-04-30',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-01',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-02',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-03',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-04',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-07',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-08',7.5)
I'd like to get a query that returns the total cost by resource
So in the case above it would be 'ResourceA', (4 * 7.5 * 40) + (3 * 7.5 * 50) = 2325.00
Can anyone provide a sample SQL query? I know this example doesn't make use of TimeType (i.e. it's always 'Normal') but I'd like to see how this is dealt with as well
I can't change the structure of the database. Many thanks in advance
IF OBJECT_ID ('tempdb..#ResourceTimeTypeCost') IS NOT NULL DROP TABLE #ResourceTimeTypeCost
CREATE TABLE #ResourceTimeTypeCost ( ResourceCode VARCHAR(32), TimeTypeCode VARCHAR(32), EffectiveDate DATETIME, CostRate DECIMAL(12,2) )
INSERT INTO #ResourceTimeTypeCost
SELECT 'ResourceA' as resourcecode, 'Normal' as timetypecode, '2012-04-30' as effectivedate, 40.00 as costrate
UNION ALL
SELECT 'ResourceA', 'Normal', '2012-05-04', 50.00
IF OBJECT_ID ('tempdb..#TimeEntered') IS NOT NULL DROP TABLE #TimeEntered
CREATE TABLE #TimeEntered ( ResourceCode VARCHAR(32), TimeTypeCode VARCHAR(32), ProjectCode VARCHAR(32), ActivityCode VARCHAR(32), TimeEnteredDate DATETIME, HoursWorked DECIMAL(12,2) )
INSERT INTO #TimeEntered
SELECT 'ResourceA','Normal','Project1','Management1','2012-04-30',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-01',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-02',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-03',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-04',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-07',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-08',7.5
;with ranges as
(
select
resourcecode
,TimeTypeCode
,EffectiveDate
,costrate
,row_number() OVER (PARTITION BY resourcecode,timetypecode ORDER BY effectivedate ASC) as row
from #ResourceTimeTypeCost
)
,ranges2 AS
(
SELECT
r1.resourcecode
,r1.TimeTypeCode
,r1.EffectiveDate
,r1.costrate
,r1.effectivedate as start_date
,ISNULL(DATEADD(ms,-3,r2.effectivedate),GETDATE()) as end_date
FROM ranges r1
LEFT OUTER JOIN ranges r2 on r2.row = r1.row + 1 --joins onto the next date row
AND r2.resourcecode = r1.resourcecode
AND r2.TimeTypeCode = r1.TimeTypeCode
)
SELECT
tee.resourcecode
,tee.timetypecode
,tee.projectcode
,tee.activitycode
,SUM(ranges2.costrate * tee.hoursworked) as total_cost
FROM #TimeEntered tee
INNER JOIN ranges2 ON tee.TimeEnteredDate >= ranges2.start_date
AND tee.TimeEnteredDate <= ranges2.end_date
AND tee.resourcecode = ranges2.resourcecode
AND tee.timetypecode = ranges2.TimeTypeCode
GROUP BY tee.resourcecode
,tee.timetypecode
,tee.projectcode
,tee.activitycode
What you have is a cost table that is, as some would say, a slowly changing dimension. First, it will help to have an effective and end date for the cost table. We can get this by doing a self join and group by:
with costs as
(select c.ResourceCode, c.EffectiveDate as effdate,
dateadd(day, -1, min(c1.EffectiveDate)) as endDate,
datediff(day, c.EffectiveDate, c1.EffectiveDate) - 1 as Span
from ResourceTimeTypeCost c left outer join
ResourceTimeTypeCost c1
group by c.ResourceCode, c.EffectiveDate
)
Although you say you cannot change the table structure, when you have a slowly changing dimension, having an effective and end date is good practice.
Now, you can use this infomation with TimeEntered as following:
select te.*, c.CostRate * te.HoursWorked as dayCost
from TimeEntered te join
Costs c
on te.ResouceCode = c.ResourceCode and
te.TimeEntered between c.EffDate and c.EndDate
To summarize by Resource for a given time range, the full query would look like:
with costs as
(select c.ResourceCode, c.EffectiveDate as effdate,
dateadd(day, -1, min(c1.EffectiveDate)) as endDate,
datediff(day, c.EffectiveDate, c1.EffectiveDate) - 1 as Span
from ResourceTimeTypeCost c left outer join
ResourceTimeTypeCost c1
group by c.ResourceCode, c.EffectiveDate
),
te as
(select te.*, c.CostRate * te.HoursWorked as dayCost
from TimeEntered te join
Costs c
on te.ResouceCode = c.ResourceCode and
te.TimeEntered between c.EffDate and c.EndDate
)
select te.ResourceCode, sum(dayCost)
from te
where te.TimeEntered >= <date1> and te.TimeEntered < <date2>
You might give this a try. CROSS APPLY will find first ResourceTimeTypeCost with older or equal date and same ResourceCode and TimeTypeCode as current record from TimeEntered.
SELECT te.ResourceCode,
te.TimeTypeCode,
te.ProjectCode,
te.ActivityCode,
te.TimeEnteredDate,
te.HoursWorked,
te.HoursWorked * rttc.CostRate Cost
FROM TimeEntered te
CROSS APPLY
(
-- First one only
SELECT top 1 CostRate
FROM ResourceTimeTypeCost
WHERE te.ResourceCode = ResourceTimeTypeCost.ResourceCode
AND te.TimeTypeCode = ResourceTimeTypeCost.TimeTypeCode
AND te.TimeEnteredDate >= ResourceTimeTypeCost.EffectiveDate
-- By most recent date
ORDER BY ResourceTimeTypeCost.EffectiveDate DESC
) rttc
Unfortunately I can no longer find article on msdn, hence the blog in link above.
Live test # Sql Fiddle.