Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Avaliação de estoque com base em FIFO no SQL Server


Surpreendentemente difícil de acertar. Suspeito que seria mais fácil usar o SQL Server 2012, que oferece suporte a somas em execução em funções de janelas. De qualquer forma:
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

A primeira observação é que não precisamos fazer nada de especial para OUT transações - só precisamos saber a quantidade total. É isso que o TotalOut CTE calcula. Os dois primeiros CTEs funcionam com IN transações e calcule o "intervalo" de estoque que cada uma representa - altere a consulta final para apenas select * from RunningTotals para sentir isso.

O SELECT final A instrução encontra linhas que não foram completamente esgotadas pelas transações de saída e, em seguida, decide se é a quantidade total dessa transação de entrada ou se essa é a transação que abrange o total de saída.