删除游标 SQL 语句

Remove cursur SQL statement

我想删除 SQL 中的游标,以提高性能(并且因为我想学习如何使用最佳实践,而最佳实践应该是基于设置的,没有游标)。

无论如何,我有一个看起来像这样的临时 table:

+------------+--------+-------+----+
|     Period | Change | Value | NR |
+------------+--------+-------+----+
|     201705 |      7 | 26055 |  1 |
|     201704 |     29 |     0 |  2 |
|     201703 |    -92 |     0 |  3 |
|     201702 |   -338 |     0 |  4 |
|     201701 |     81 |     0 |  5 |
|     201612 |    107 |     0 |  6 |
|     201611 |     72 |     0 |  7 |
|     201610 |     54 |     0 |  8 |
|     201609 |     64 |     0 |  9 |
|     201608 |     47 |     0 | 10 |
|     201607 |     23 |     0 | 11 |
|     201606 |     45 |     0 | 12 |
+------------+--------+-------+----+

目前,游标的作用如下:

DECLARE  @Value INT    
BEGIN    
DECLARE c_Value CURSOR FOR
    SELECT NR  
    FROM ##TMP
    WHERE Value = 0    
----              
OPEN c_Value        
FETCH NEXT FROM c_Value 
INTO @Value

WHILE @@FETCH_STATUS = 0 
BEGIN           
    SELECT  @Value = Value - Change
    FROM ##TMP
    WHERE NR = (Select  MAX(NR) From ##TMP WHERE Value <> 0)                   
                    BEGIN                   
                        UPDATE ##TMP
                        SET Value = @Value 
                        WHERE NR = (Select  MAX(NR)+1 From ##TMP WHERE Value <> 0) 
                    END                      
       FETCH NEXT FROM c_Value
          INTO @Value             
          END

  CLOSE c_Value
  DEALLOCATE c_Value      
END

结果:

+------------+--------+-------+----+
|     Period | Change | Value | NR |
+------------+--------+-------+----+
|     201705 |      7 | 26055 |  1 |
|     201704 |     29 | 26048 |  2 |
|     201703 |    -92 | 26019 |  3 |
|     201702 |   -338 | 26111 |  4 |
|     201701 |     81 | 26449 |  5 |
|     201612 |    107 | 26368 |  6 |
|     201611 |     72 | 26261 |  7 |
|     201610 |     54 | 26189 |  8 |
|     201609 |     64 | 26135 |  9 |
|     201608 |     47 | 26071 | 10 |
|     201607 |     23 | 26024 | 11 |
|     201606 |     45 | 26001 | 12 |
+------------+--------+-------+----+

那么,如何在不使用游标的情况下实现这个结果呢?我用 CTE 试过,但我得不到这个结果。

首先你需要得到起始值。

SELECT [Value] as StartValue
FROM Table1
WHERE NR = 1

然后使用累积 SUM() 你可以修改起始值,注意你必须忽略每一行的 [Change]

SQL DEMO

WITH CTE as (
    SELECT [Value] as StartValue
    FROM Table1
    WHERE NR = 1
)    
SELECT T.*, 
       - SUM(CHANGE) OVER (ORDER BY [NR]) 
       + [CHANGE] as TotalChange, -- just for debug, dont need this.      
       CTE.StartValue 
        - SUM([CHANGE]) OVER (ORDER BY [NR]) 
        + [CHANGE] as NewValue
FROM Table1 T
CROSS JOIN CTE

输出

这可以通过使用 SQL Server 2014 中引入的窗口函数来解决。

select period, 
change, 
NR = Row_Number() Over(Order by period), 
Value = Sum(Change) Over(Order by period rows unbounded preceding)

这是徒手写的,可能无法解析,但应该足够接近了。

SQL 服务器 2012 或更高版本:

CREATE TABLE ##TMP (
  Period int
 ,Change float
 ,Value float
 ,Nr int
);
INSERT INTO ##TMP VALUES
 (201705,     7 , 26055, 1)
,(201704,    29 ,     0, 2)
,(201703,   -92 ,     0, 3)
,(201702,  -338 ,     0, 4)
,(201701,    81 ,     0, 5)
,(201612,   107 ,     0, 6)
,(201611,    72 ,     0, 7)
,(201610,    54 ,     0, 8)
,(201609,    64 ,     0, 9)
,(201608,    47 ,     0,10)
,(201607,    23 ,     0,11)
,(201606,    45 ,     0,12)

;with cte as (
SELECT Period, Change, value as Value_Org, Nr, SUM(Value - Change) OVER (ORDER BY Nr ASC ) as Value
FROM ##TMP
)
select a.Period, a.Change, a.nr, a.value_org, a.value, b.value, 
isnull(b.value, a.value_org)
from cte as a 
left outer join cte as b
on a.nr = b.nr+1
order by a.Nr