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

时间:2016-06-15 11:52:30

标签: sql-server-2012 lag

如何使用 LAG 功能获取更新的上一行值(不使用递归CTE )。请查看截图以获取示例输出

enter image description here

查询已尝试

 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

enter image description here

更新 可以使用递归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

enter image description here

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

2 个答案:

答案 0 :(得分:1)

这里的表现受到递归 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 s的任何总数超过Credit的总和,则会失败。如果BlockTotal为负数,则必须将其与一个或多个后续块合并,并且无需迭代或递归即可完成。

在现实生活中,我会将CTE3转储到临时表中并循环合并块,直到不再有负BlockTotal为止。

答案 1 :(得分:0)

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

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

<强>输入:

enter image description here

<强>输出:

enter image description here