sql >> Base de Datos >  >> RDS >> Sqlserver

Valoración de inventario de existencias basada en FIFO en SQL Server

Sorprendentemente difícil de acertar. Sospecho que sería más fácil usar SQL Server 2012, que admite la ejecución de sumas en funciones de ventanas. De todos modos:

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

La primera observación es que no necesitamos hacer nada especial para OUT transacciones:solo necesitamos saber la cantidad total. Eso es lo que TotalOut CTE calcula. Los dos primeros CTE funcionan con IN transacciones y calcule qué "intervalo" de existencias representa cada una:cambie la consulta final a simplemente select * from RunningTotals para tener una idea de eso.

El SELECT final encuentra filas que no han sido completamente agotadas por transacciones salientes, y luego decide si es la cantidad total de esa transacción entrante, o si esa es la transacción que se extiende a ambos lados del total saliente.