删除游标 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]
值
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
我想删除 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]
值
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