Zaskakująco trudno to zrobić. Podejrzewam, że łatwiej byłoby użyć SQL Server 2012, który obsługuje uruchamianie sum w funkcjach okienkowych. W każdym razie:
declare @Stock table (Item char(3) not null,[Date] datetime not null,TxnType varchar(3) not null,Qty int not null,Price decimal(10,2) null)
insert into @Stock(Item , [Date] , TxnType, Qty, Price) values
('ABC','20120401','IN', 200, 750.00),
('ABC','20120405','OUT', 100 ,null ),
('ABC','20120410','IN', 50, 700.00),
('ABC','20120416','IN', 75, 800.00),
('ABC','20120425','OUT', 175, null ),
('XYZ','20120402','IN', 150, 350.00),
('XYZ','20120408','OUT', 120 ,null ),
('XYZ','20120412','OUT', 10 ,null ),
('XYZ','20120424','IN', 90, 340.00);
;WITH OrderedIn as (
select *,ROW_NUMBER() OVER (PARTITION BY Item ORDER BY [DATE]) as rn
from @Stock
where TxnType = 'IN'
), RunningTotals as (
select Item,Qty,Price,Qty as Total,0 as PrevTotal,rn from OrderedIn where rn = 1
union all
select rt.Item,oi.Qty,oi.Price,rt.Total + oi.Qty,rt.Total,oi.rn
from
RunningTotals rt
inner join
OrderedIn oi
on
rt.Item = oi.Item and
rt.rn = oi.rn - 1
), TotalOut as (
select Item,SUM(Qty) as Qty from @Stock where TxnType='OUT' group by Item
)
select
rt.Item,SUM(CASE WHEN PrevTotal > out.Qty THEN rt.Qty ELSE rt.Total - out.Qty END * Price)
from
RunningTotals rt
inner join
TotalOut out
on
rt.Item = out.Item
where
rt.Total > out.Qty
group by rt.Item
Pierwsza obserwacja jest taka, że nie musimy robić nic specjalnego dla OUT
transakcje - wystarczy znać całkowitą ilość. To właśnie TotalOut
CTE oblicza. Pierwsze dwa CTE działają z IN
transakcji i oblicz, jaki „interwał” reprezentuje każdy z nich — zmień końcowe zapytanie, aby po prostu select * from RunningTotals
aby to wyczuć.
Ostatni SELECT
instrukcja znajduje wiersze, które nie zostały całkowicie wyczerpane przez transakcje wychodzące, a następnie decyduje, czy jest to cała ilość tej transakcji przychodzącej, czy też jest to transakcja, która obejmuje sumę wychodzącą.