使用 LAG 获取上一行更新值而不使用递归 CTE

Get previous row updated value using LAG Without using Recursive CTE

如何使用 LAG 函数获取更新的前一行值(不使用 Recursive CTE)。请查看示例输出的屏幕截图

已尝试查询

 Declare @Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into @Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)

Select
    T.SNO,
    T.Credit,
    T.Debit,
    TotalDebit = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then Debit + (LAG(T.Debit, 1, 0) OVER (ORDER BY SNO)-Credit) Else Debit End,
    Amount = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then 0 Else Credit-LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) End,
    T.PaidDate
From @Tbl T

更新: 可以使用递归 CTE 获得预期的结果,但是当我将查询转换为函数并且当我加入具有 3000 条记录的函数时,需要很长时间才能执行。这就是为什么我试图在没有递归 CTE 部分的情况下转换查询。

递归 CTE 查询:

Declare @Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into @Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)


;With Temp As(/* Detect Debited amount */
    Select Top 1 SNO,Credit,Debit,Debit As TotalDebit,Credit As Amount,PaidDate From @Tbl
    Union All
    Select
        R.SNO,
        R.Credit,
        R.Debit,
        TotalDebit = Case When R.Credit < RP.TotalDebit Then R.Debit + (RP.TotalDebit-R.Credit) Else R.Debit End,
        Amount = Case When R.Credit < RP.TotalDebit Then 0 Else R.Credit-RP.TotalDebit End,
        R.PaidDate
    From @Tbl R
    Inner Join Temp RP ON R.SNO-1=RP.SNO
)

Select * From Temp

电子表格示例: https://docs.google.com/spreadsheets/d/1FNwzgGxmLiLFS_R5QANnfd16Iw64xhF0gWTc4ZocKsk/edit?usp=sharing

此处的性能受到递归 CTE 的影响。 CTE 本身只是语法糖。

仅针对此特定示例数据,无需递归即可工作:

Declare @Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into @Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate);

With CTE1 As (
    Select *
      , CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY SNO) ELSE 0 END As LastCrPerBlock
    From @Tbl
), CTE2 As (
    Select *
      , SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
    From CTE1
), CTE3 As (
    Select *
      , SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
      , SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
    From CTE2
)
Select SNO, Credit, Debit
  , CASE WHEN BlockRunningTotal < 0 THEN -BlockRunningTotal ELSE 0 END As TotalDebit
  , CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END As Amount
  , PaidDate
From CTE3
Order By SNO;

这有助于评估性能,但如果在任何块中 Debit 的总数超过 Credit 的总数,它将失败。如果 BlockTotal 是负数,那么它必须与一个或几个后续块合并,并且如果没有迭代或递归就无法完成。

在现实生活中,我会将 CTE3 转储到临时 table 并循环合并块,直到不再有负面 BlockTotals。

来自 Y.B's 的答案,添加了递归 CTE 来处理任何 BlockTotal 是否为负数。无法使用 while 循环进行递归,因为我将此查询转换为内联 table 值函数。(多语句 table 值函数非常慢)

Declare @Tbl as Table(ReceiptNo varchar(50),Credit Money,Debit Money,PaidDate Date)
Insert into @Tbl
SELECT * FROM (VALUES ('R1',20,0,'1Jan16'),('R2',0,2,'2Jan16'),('R3',0,3,'3Jan16'),('R4',0,5,'4Jan16'),('R5',10,0,'5Jan16'),('R6',0,1,'6Jan16'),('R7',0,10,'7Jan16')) AS X(ReceiptNo,Credit,Debit,PaidDate);

With Receipts As (
    Select 
        SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Credit,Debit,PaidDate,
        LastCrPerBlock = CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY PaidDate DESC) ELSE 0 END
    From @Tbl
), Blocks As (
    Select *
      , SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
    From Receipts
), BlockTotal As (
    Select *
      , SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
      , SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
    From Blocks
),
ReceiptAmount As (
    Select ReceiptNo,
        Amount = CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END,
        Debit = IIF(BlockNumber<>LEAD(BlockNumber) OVER(ORDER BY SNO) and BlockRunningTotal<0,ABS(BlockRunningTotal),0),
        PaidDate
    From BlockTotal
),
FinalReceipt2012 As (
    Select 
        SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Amount,Debit,PaidDate,
        Recur = IIF(Exists(Select Top 1 R1.Amount From ReceiptAmount R1 Where Debit>0),1,0)
    From ReceiptAmount
    Where Amount>0 or Debit>0
),
FinalReceipt As (
    Select * From FinalReceipt2012 Where Recur=0 OR SNO=1
    Union All
    Select
        R.SNO,R.ReceiptNo,
        Amount = Case When R.Amount < RP.Debit Then 0 Else R.Amount-RP.Debit End,
        Debit = Case When R.Amount < RP.Debit Then R.Debit + (RP.Debit-R.Amount) Else R.Debit End,
        R.PaidDate,0 As Recur
    From FinalReceipt2012 R 
    Inner Join FinalReceipt RP ON R.SNO=RP.SNO+1
    Where R.Recur=1
)

Select ReceiptNo,Amount,PaidDate From FinalReceipt Where Amount>0

输入:

输出: