试着避免使用光标

时间:2015-02-12 20:48:18

标签: sql-server sql-server-2008 tsql

我已经获得了一个查询并尝试找出一种方法来删除光标并保持功能,因为起始表可以进入数百万行。

表格中的数据示例:

ID   DollarValue  Month     RowNumber
1     $10         1/1/2014  1
1     $15         2/1/2014  2
1    -$40         3/1/2014  3
1     $50         4/1/2014  4
2    -$11         1/1/2014  1
2     $11         2/1/2014  2
2     $5          3/1/2014  3

预期结果:

ID   DollarValue  Month     RowNumber  TestVal
1     $10         1/1/2014  1           1 
1     $15         2/1/2014  2           0
1    -$40         3/1/2014  3          -1
1     $50         4/1/2014  4           1
2    -$11         1/1/2014  1          -1
2     $11         2/1/2014  2           0
2     $5          3/1/2014  3           1

这是光标内部发生的逻辑(伪代码):

If a @ID <> @LastId AND @Month <> @LastMonth
  Set @RunningTotal = @DollarValue
  Set @LastMonth = '12/31/2099'
  Set @LastID = @ID
  Set @TestVal = Sign(@DollarValue)
Else
  If Sign(@RunningTotal) = Sign(@RunningTotal + @DollarValue)
    Set @TestVal = 0
Else
   Set @TestVal = Sign(@DollarValue)

Set @RunningTotal = @RunningTotal + @DollarValue

我知道如何将其更改为基于设置?

2 个答案:

答案 0 :(得分:2)

您可以使用窗口版SUM来计算运行总计:

;WITH CTE AS (
   SELECT ID, DollarValue, Month, RowNumber,
          SUM ( DollarValue ) OVER (PARTITION BY ID ORDER BY RowNumber) as RunningTotal
   FROM #mytable
)
SELECT C1.ID, C1.DollarValue, C1.Month, C1.RowNumber,
       CASE WHEN C1.RowNumber = 1 THEN SIGN(C1.DollarValue)
            WHEN SIGN(C1.RunningTotal) = SIGN(C2.RunningTotal) THEN 0
            ELSE SIGN(C1.RunningTotal) 
       END AS TestVal        
FROM CTE AS C1
LEFT JOIN CTE AS C2 ON C1.ID = C2.ID AND C1.RowNumber = C2.RowNumber + 1

LEFT JOIN上使用RowNumber,您可以获取之前的记录,并将当前的运行总计与之前的记录进行比较。然后使用简单的CASE来应用与运行总计SIGN中的更改相关的规则。

SQL FIDDLE Demo

P.S。似乎上述解决方案在SQL Server 2012之前的版本中不起作用。在这种情况下,CTE内的运行总计算必须由“常规”版本替换。

答案 1 :(得分:2)

这是2008年的解决方案

WITH CTE AS (
SELECT 
  AA.[ID]
 ,AA.[Month]
 ,AA.[RowNumber]
 ,AA.[DollarValue]
 ,SIGN(SUM(BB.[DollarValue])) AS RunTotalSign

FROM YourTable AS AA
LEFT JOIN YourTable AS BB
       ON (AA.[ID] = BB.[ID] AND BB.[RowNumber] <= AA.[RowNumber])
GROUP BY  AA.[ID],AA.[Month],AA.[DollarValue],AA.[RowNumber])
)

SELECT 
  AA.[ID]
 ,AA.[Month]
 ,AA.[RowNumber]
 ,AA.[DollarValue]
 ,CASE WHEN AA.RunTotalSign = CC.RunTotalSign Then 0
       ELSE AA.RunTotalSign
       END
  AS TestVal
FROM CTE AS AA
LEFT JOIN CTE AS CC
       ON (AA.[ID] = CC.[ID] AND AA.[RowNumber] = CC.[RowNumber]+1)