【问题标题】:SQL Query to calculate remaining running balances based on a given conditions根据给定条件计算剩余运行余额的 SQL 查询
【发布时间】:2012-05-11 08:17:53
【问题描述】:

我有一个这样的股票交易表:

StockID Item  TransDate   TranType  BatchNo Qty Price 
10001   ABC   01-Apr-2012   IN     71001000 200  750.0
10002   ABC   02-Apr-2012   OUT             100       
10003   ABC   03-Apr-2012   IN     71001001  50  700.0
10004   ABC   04-Apr-2012   IN     71001002  75  800.0
10005   ABC   10-Apr-2012   OUT             125       
10006   XYZ   05-Apr-2012   IN     71001003 150  350.0
10007   XYZ   05-Apr-2012   OUT             120       
10008   XYZ   15-Apr-2012   OUT              10       
10009   XYZ   20-Apr-2012   IN     71001004  90  340.0
10010   PQR   06-Apr-2012   IN     71001005  50  510.0
10011   PQR   15-Apr-2012   IN     71001006  60  505.0
10012   MNO   01-Apr-2012   IN     71001007  76  410.0
10013   MNO   11-Apr-2012   OUT              76 

我的每笔 IN 交易都有与之相关的价格和批号(批号)。现在我想通过先进先出(FIFO)规则计算剩余数量,这意味着先进应该与先出进行调整。调整数量后,将针对同一项目的每个 IN 交易计算剩余余额,如下所示:

StockID Item  TransDate   TranType  BatchNo Qty Price  RemainingQty
10001   ABC   01-Apr-2012   IN     71001000 200  750.0    0        
10002   ABC   02-Apr-2012   OUT             100             
10003   ABC   03-Apr-2012   IN     71001001  50  700.0   25        
10004   ABC   04-Apr-2012   IN     71001002  75  800.0   75        
10005   ABC   10-Apr-2012   OUT             125             
10006   XYZ   05-Apr-2012   IN     71001003 150  350.0   20        
10007   XYZ   05-Apr-2012   OUT             120             
10008   XYZ   15-Apr-2012   OUT              10             
10009   XYZ   20-Apr-2012   IN     71001004  90  340.0   90        
10010   PQR   06-Apr-2012   IN     71001005  50  510.0   50        
10011   PQR   15-Apr-2012   IN     71001006  60  505.0   60        
10012   MNO   01-Apr-2012   IN     71001007  76  410.0   0         
10013   MNO   11-Apr-2012   OUT              76                    

从上表 ABC 中我们可以看出,在使用 FIFO 调整 (125 + 100) OUT 数量与 IN 数量 (100 + 50 + 75) 后,批次 71001000 的剩余数量为 0,71001001 为 25批次 71001002 为 75。从剩余数量可以得出该值。

请帮助我使用任何方法(基于光标或 CTE 或 JOINS 等)实现此目的 在此先感谢您的帮助。

StockOverflow 的一位用户提出了这个答案:

SELECT 10001   as stockid,'ABC' as item,'01-Apr-2012' as transdate,'IN' as trantype,     71001000 as batchno, 200 as qty,  750.0  as price INTO #sample
UNION ALL SELECT 10002   ,'ABC','02-Apr-2012','OUT', NULL            ,100,NULL        
UNION ALL SELECT 10003   ,'ABC','03-Apr-2012','IN',     71001001,  50 , 700.0 
UNION ALL SELECT 10004   ,'ABC','04-Apr-2012','IN',     71001002,  75 , 800.0 
UNION ALL SELECT 10005   ,'ABC','10-Apr-2012','OUT',     NULL        ,125,NULL        
UNION ALL SELECT 10006   ,'XYZ','05-Apr-2012','IN',     71001003, 150 , 350.0 
UNION ALL SELECT 10007   ,'XYZ','05-Apr-2012','OUT',      NULL      , 120    ,NULL    
UNION ALL SELECT 10008   ,'XYZ','15-Apr-2012','OUT',       NULL     ,  10        ,NULL
UNION ALL SELECT 10009   ,'XYZ','20-Apr-2012','IN',     71001004,  90 , 340.0 
UNION ALL SELECT 10010   ,'PQR','06-Apr-2012','IN',     71001005,  50 , 510.0 
UNION ALL SELECT 10011   ,'PQR','15-Apr-2012','IN',     71001006,  60 , 505.0 
UNION ALL SELECT 10012   ,'MNO','01-Apr-2012','IN',     71001007,  76 , 410.0 
UNION ALL SELECT 10013   ,'MNO','11-Apr-2012','OUT',    NULL    ,76 ,NULL


;WITH remaining AS
(
    SELECT *,
           CASE 
                WHEN trantype = 'IN' THEN 1
                ELSE -1
           END * qty AS stock_shift,
           ROW_NUMBER() OVER(PARTITION BY item ORDER BY transdate) AS row,
           CASE 
                WHEN trantype = 'OUT' THEN NULL
                ELSE ROW_NUMBER()OVER(PARTITION BY item, CASE WHEN trantype = 'IN' THEN 0 ELSE 1 END ORDER BY transdate)
           END AS in_row,
           SUM(CASE WHEN trantype = 'OUT' THEN qty END) OVER(PARTITION BY item) AS total_out
    FROM   #sample
)
,remaining2 AS
(
    SELECT r1.item,
           r1.stockid,
           MAX(r1.transdate) AS transdate,
           MAX(r1.trantype) AS trantype,
           MAX(r1.batchno) AS batchno,
           MAX(r1.qty) AS qty,
           MAX(r1.price) AS price,
           MAX(r1.total_out) AS total_out,
           MAX(r1.in_row) AS in_row,
           CASE 
                WHEN MAX(r1.trantype) = 'OUT' THEN NULL
                WHEN SUM(CASE WHEN r1.trantype = 'IN' THEN r2.qty ELSE 0 END) - MAX(r1.total_out) < 0 THEN SUM(CASE WHEN r1.trantype = 'IN' THEN r2.qty ELSE 0 END) 
                     - MAX(r1.total_out)
                ELSE 0
           END AS running_in
    FROM   remaining r1
           LEFT OUTER JOIN remaining r2
                ON  r2.row <= r1.row
                AND r2.item = r1.item
    GROUP BY
           r1.item,
           r1.stockid
)
SELECT r2.item,
       r2.stockid,
       MAX(r2.transdate) AS transdate,
       MAX(r2.trantype) AS trantype,
       MAX(r2.batchno) AS batchno,
       MAX(r2.qty) AS qty,
       MAX(r2.price) AS price,
       MAX(CASE WHEN r2.trantype = 'OUT' THEN NULL ELSE ISNULL(r2.qty + r3.running_in, 0) END) AS remaining_stock
FROM   remaining2 r2
       LEFT OUTER JOIN remaining2 r3
            ON  r2.in_row - 1 = r3.in_row
            AND r2.item = r3.item
GROUP BY
       r2.item,
       r2.stockid

这个sql有问题,这里附上结果值不匹配的记录用黄色表示。请帮助解决问题。

【问题讨论】:

  • 显而易见的问题是,为什么没有为 OUT 记录分配 BatchNo?它会让事情更容易解决你的问题。您的问题清楚地说明了这种关系,但您的表定义并未反映这一点。
  • 嗨@Brad,批号只是为识别批次而创建的交易号。然而,所有的批次都是物理混合在一起并发出的。但出于会计目的,结果是必需的。
  • @Nagesh 我已经修改了下面的原始答案,适用于您的示例数据

标签: tsql sql-server-2005


【解决方案1】:

我认为这应该可以解决问题?

SELECT 10001   as stockid,'ABC' as item,'01-Apr-2012' as transdate,'IN' as trantype,     71001000 as batchno, 200 as qty,  750.0  as price INTO #sample
UNION ALL SELECT 10002   ,'ABC','02-Apr-2012','OUT', NULL            ,100,NULL        
UNION ALL SELECT 10003   ,'ABC','03-Apr-2012','IN',     71001001,  50 , 700.0 
UNION ALL SELECT 10004   ,'ABC','04-Apr-2012','IN',     71001002,  75 , 800.0 
UNION ALL SELECT 10005   ,'ABC','10-Apr-2012','OUT',     NULL        ,125,NULL        
UNION ALL SELECT 10006   ,'XYZ','05-Apr-2012','IN',     71001003, 150 , 350.0 
UNION ALL SELECT 10007   ,'XYZ','05-Apr-2012','OUT',      NULL      , 120    ,NULL    
UNION ALL SELECT 10008   ,'XYZ','15-Apr-2012','OUT',       NULL     ,  10        ,NULL
UNION ALL SELECT 10009   ,'XYZ','20-Apr-2012','IN',     71001004,  90 , 340.0 
UNION ALL SELECT 10010   ,'PQR','06-Apr-2012','IN',     71001005,  50 , 510.0 
UNION ALL SELECT 10011   ,'PQR','15-Apr-2012','IN',     71001006,  60 , 505.0 
UNION ALL SELECT 10012   ,'MNO','01-Apr-2012','IN',     71001007,  76 , 410.0 
UNION ALL SELECT 10013,'MNO','11-Apr-2012','OUT',          NULL    ,76 ,NULL

;with remaining_stock as
( 
SELECT * 
,CASE WHEN trantype = 'IN' THEN 1 ELSE -1 END * qty AS stock_shift
,row_number() OVER (PARTITION BY item ORDER BY transdate) as row
,CASE WHEN trantype = 'OUT' THEN NULL ELSE
row_number()OVER (PARTITION BY item,CASE WHEN trantype = 'IN' THEN 0 ELSE 1 END ORDER BY transdate) END as in_row
,CASE WHEN trantype = 'IN' THEN NULL ELSE
row_number()OVER (PARTITION BY item,CASE WHEN trantype = 'OUT' THEN 0 ELSE 1 END ORDER BY transdate) END as out_row
,ISNULL(SUM(CASE WHEN trantype = 'OUT' THEN qty END) OVER (PARTITION BY item),0) AS total_out
,ISNULL(SUM(CASE WHEN trantype = 'IN' THEN qty END) OVER (PARTITION BY item),0) AS total_in
FROM #sample
)
,remaining_stock2 AS
(
SELECT 
r1.item
,r1.stockid
,MAX(r1.transdate) as transdate
,MAX(r1.trantype) as trantype
,MAX(r1.batchno) as batchno
,MAX(r1.qty) as qty
,MAX(r1.price) as price
,MAX(r1.total_in) as total_in
,MAX(r1.total_out) as total_out
,SUM(r2.qty) as running_in
FROM remaining_stock r1 
LEFT OUTER JOIN remaining_stock r2 on r2.in_row <= r1.in_row
                    AND r2.item = r1.item       
GROUP BY
r1.item
,r1.stockid 
)
SELECT
item
,stockid
,transdate
,trantype
,batchno
,qty
,price
,CASE WHEN  trantype = 'OUT' THEN NULL
        WHEN total_out >= running_in THEN 0 
        WHEN (running_in - total_out) < qty THEN (running_in - total_out)
        WHEN (running_in - total_out) >= qty THEN qty 
        END as remaining_stocks
FROM remaining_stock2

【讨论】:

  • 亲爱的@Davin,感谢您的回答,您是提供第一个解决方案的人。所有功劳归你。让我检查并确认这一点。
【解决方案2】:

关于如何应用 FIFO 逻辑,您的问题对我来说不是很清楚。我将假设您希望将每个 IN 记录与下一个 OUT 记录(如果存在)相关联。要实现这一点,您需要像下面这样加入表本身

select
    t1.BatchNo,
    isnull(t1.Qty,0)  as 'IN Qty',
    isnull(t2.Qty,0)  as 'OUT Qty',
    isnull(t1.Qty,0) - isnull(t2.Qty,0) as 'Remaining Qty'
from 
    tbl_test t1
left join tbl_test t2
    on t2.StockID = (t1.StockID + 1)
    and t2.TranType = 'OUT'
where
    t1.TranType = 'IN'

结果将显示您问题中ABC 的前 5 条记录的以下内容。

BatchNo  | IN Qty | OUT Qty | Remaining Qty
71001000 | 200    | 100     | 100
71001001 | 50     | 0       | 50
71001002 | 75     | 125     | -50

左连接假设每个 IN 记录的 StockID 总是比关联的 OUT 记录少一个数字。我个人认为您的数据模型需要改进。

  • OUT 记录应分配有 BatchNo 或对 关联 IN 记录的 StockID
  • 为顺序排序添加时间戳字段
  • 添加一个 DateTime 字段来处理同一天发生的 IN/OUT

【讨论】:

  • 嗨@Brad,时间戳和日期时间存在于数据模型中,为简单起见,此处未显示。但是 BatchNo 引用不存在,这就是数据模型的设计方式。请帮助实现所需的结果。
  • 如果您能进一步阐明逻辑,我可以尝试进一步回答。我不明白为什么当 BatchNo 71001000 的 IN = 200 和 OUT = 100 时,您想要的结果中的第一条记录有 RemainingQty = 0。我原以为 RemainingQty = 100
  • 感谢@Brad 的回复。对于项目 ABC,有 5 个事务 - 3 个 IN 和 2 个 OUT。所有 IN 的净流入量为 325,OUT 的净流出量为 225,净剩余量为 100。这 100 个数量将根据 FIFO 分配给每个批次。由于 200 Qty 是第一个 IN 交易,因此它被取消。
  • OK,所以表中 ABC 的总数量 = 100。我可以看到为什么 BatchNo 71001000 会显示 zero(即 100 的总和 - 100 的批次 IN-OUT),但我不明白为什么 BatchNo 71001001 会显示 25?因为没有剩余的数量可以分配
  • 对于批次 71001000,数量为 200,下一个交易为 OUT,数量为 100,因此余额为 100。随后有两个 IN 交易,数量为 50 和 75。所以总库存数量是(100+50+75)=225。这两个IN之后的下一个交易是125数量的OUT。现在这 125 必须使用 FIFO 进行调整。因此,125 个中的 100 个已针对批次 71001000 的剩余 100 个数量进行了调整。剩余的 25 个已针对批次 71001001 进行了调整,因为它是下一个 IN 交易。希望我的解释足够。
猜你喜欢
  • 1970-01-01
  • 2014-06-02
  • 2017-06-11
  • 1970-01-01
  • 2017-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多