Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Obtenir la valeur mise à jour de la ligne précédente à l'aide de LAG sans utiliser de CTE récursif

Les performances ici souffrent de récursif CTE. Le CTE en lui-même n'est que du sucre syntaxique.

Juste pour cet exemple de données particulier, cela fonctionne sans récursivité :

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;

Cela peut aider à évaluer les performances, mais cela échouera si dans n'importe quel bloc total de Debit s dépasse le total de Credit s. Si BlockTotal est négatif alors il doit être fusionné avec un ou plusieurs blocs suivants et cela ne peut se faire sans itération ni récursivité.

Dans la vraie vie, je viderais CTE3 dans une table temporaire et la parcourrais en fusionnant des blocs jusqu'à ce qu'il n'y ait plus de BlockTotal négatif s.