11

使用 SQL 2005 / 2008

我必须使用前向光标,但我不想表现不佳。有没有更快的方法可以在不使用游标的情况下循环?

4

9 回答 9

11

这是使用游标的示例:

DECLARE @VisitorID int
DECLARE @FirstName varchar(30), @LastName varchar(30)
-- declare cursor called ActiveVisitorCursor 
DECLARE ActiveVisitorCursor Cursor FOR
SELECT VisitorID, FirstName, LastName 
FROM Visitors
WHERE Active = 1
-- Open the cursor
OPEN ActiveVisitorCursor 
-- Fetch the first row of the cursor and assign its values into variables
FETCH NEXT FROM ActiveVisitorCursor INTO @VisitorID, @FirstName, @LastName 
-- perform action whilst a row was found
WHILE @@FETCH_STATUS = 0
BEGIN
 Exec MyCallingStoredProc @VisitorID, @Forename, @Surname
 -- get next row of cursor
 FETCH NEXT FROM ActiveVisitorCursor INTO @VisitorID, @FirstName, @LastName 
END
 -- Close the cursor to release locks
CLOSE ActiveVisitorCursor 
 -- Free memory used by cursor
DEALLOCATE ActiveVisitorCursor 

现在这里是示例如何在不使用游标的情况下获得相同的结果:

/* Here is alternative approach */

-- Create a temporary table, note the IDENTITY
-- column that will be used to loop through
-- the rows of this table
CREATE TABLE #ActiveVisitors (
       RowID int IDENTITY(1, 1), 
       VisitorID int,
       FirstName varchar(30),
       LastName varchar(30)
 )
DECLARE @NumberRecords int, @RowCounter int
DECLARE @VisitorID int, @FirstName varchar(30), @LastName varchar(30)

-- Insert the resultset we want to loop through
-- into the temporary table
INSERT INTO #ActiveVisitors (VisitorID, FirstName, LastName)
SELECT VisitorID, FirstName, LastName
FROM Visitors
WHERE Active = 1 

-- Get the number of records in the temporary table
SET @NumberRecords = @@RowCount 
--You can use: SET @NumberRecords = SELECT COUNT(*) FROM #ActiveVisitors
SET @RowCounter = 1

-- loop through all records in the temporary table
-- using the WHILE loop construct
WHILE @RowCounter <= @NumberRecords
BEGIN
 SELECT @VisitorID = VisitorID, @FirstName = FirstName, @LastName = LastName 
 FROM #ActiveVisitors
 WHERE RowID = @RowCounter

 EXEC MyCallingStoredProc @VisitorID, @FirstName, @LastName

 SET @RowCounter = @RowCounter + 1
END

-- drop the temporary table
DROP TABLE #ActiveVisitors
于 2018-05-14T09:45:08.800 回答
6

“从不使用游标”是一个很好的例子,说明了简单规则的破坏性。是的,它们很容易沟通,但是当我们去掉规则的原因,以便我们有一个“易于遵循”的规则时,大多数人只会盲目地遵循规则而不考虑它,即使遵循规则已经负面影响。

游标,至少在 SQL Server / T-SQL 中,被极大地误解了。说“游标影响 SQL 的性能”是不准确的。他们当然有这种倾向,但这在很大程度上与人们如何使用它们有关。如果使用得当,游标比循环更快、更高效、更不容易出错WHILE(是的,这是真的,并且已经被一遍又一遍地证明,不管谁认为“游标是邪恶的”)。

第一种选择是尝试找到一种基于集合的方法来解决问题。

如果逻辑上没有基于集合的方法(例如,需要对EXEC每一行调用),并且对游标的查询正在命中真实(非临时)表,则使用将语句结果放入内部的STATIC关键字SELECT临时表,因此在您遍历结果时不会锁定查询的基表。默认情况下,游标对查询的基础表中的更改“敏感”,并将验证这些记录在您调用时是否仍然存在FETCH NEXT(因此游标通常被视为缓慢的原因的很大一部分)。如果您需要对在处理结果集时可能会消失的记录保持敏感,则使用STATIC将无济于事,但如果您正在考虑转换为WHILE针对临时表循环(因为它也不知道基础数据的更改)。

如果游标的查询仅从临时表和/或表变量中进行选择,那么您不需要阻止锁定,因为在这些情况下您没有并发问题,在这种情况下您应该使用FAST_FORWARD而不是STATIC.

我认为指定 的三个选项也有帮助LOCAL READ_ONLY FORWARD_ONLY,除非您特别需要一个不是其中一个或多个的游标。但我还没有测试过它们是否能提高性能。

假设操作不符合基于集合的条件,那么以下选项对于大多数操作来说是一个很好的起点:

DECLARE [Thing1] CURSOR LOCAL READ_ONLY FORWARD_ONLY STATIC
FOR SELECT columns
    FROM   Schema.ReadTable(s);


DECLARE [Thing2] CURSOR LOCAL READ_ONLY FORWARD_ONLY FAST_FORWARD
FOR SELECT columns
    FROM   #TempTable(s) and/or @TableVariables;
于 2011-03-24T21:45:41.787 回答
5

您可以执行WHILE循环,但是您应该寻求实现更多基于集合的操作,因为 SQL 中的任何迭代都会受到性能问题的影响。

http://msdn.microsoft.com/en-us/library/ms178642.aspx

于 2011-03-24T21:20:05.923 回答
4

正如@Neil 建议的那样,Common Table Expressions 将是一个不错的选择。这是 Adventureworks 的一个示例:

WITH cte_PO AS 
(
SELECT [LineTotal]
  ,[ModifiedDate]
FROM [AdventureWorks].[Purchasing].[PurchaseOrderDetail]
),
minmax AS
(
    SELECT MIN([LineTotal]) as DayMin
        ,MAX([LineTotal]) as DayMax
        ,[ModifiedDate]
    FROM cte_PO
    GROUP BY [ModifiedDate]
)
SELECT * FROM minmax ORDER BY ModifiedDate

这是它返回的前几行:

DayMin     DayMax     ModifiedDate
135.36     8847.30    2001-05-24 00:00:00.000
129.8115   25334.925  2001-06-07 00:00:00.000
于 2011-03-25T02:12:25.890 回答
3

我必须使用前向光标,但我不想表现不佳。有没有更快的方法可以在不使用游标的情况下循环?

这取决于您对光标的操作。

几乎所有东西都可以使用基于集合的操作重写,在这种情况下,循环在查询计划内执行,因为它们不涉及上下文切换,所以速度要快得多。

但是,有些事情SQL Server并不擅长,例如计算累积值或加入日期范围。

使用以下命令可以更快地进行这些类型的查询CURSOR

但同样,这是一个非常罕见的例外,通常基于集合的方式表现更好。

如果您发布了查询,我们可能会对其进行优化并删除CURSOR.

于 2011-03-25T11:22:05.593 回答
2

使用公用表表达式的递归查询

于 2011-03-24T21:19:45.950 回答
2

根据您的需要,您可以使用计数表。

Jeff Moden 有一篇关于计数表的优秀文章在这里

于 2011-03-24T22:00:23.533 回答
2

不要使用游标,而是寻找基于集合的解决方案。如果您找不到基于集合的解决方案......仍然不要使用光标!发布您要实现的目标的详细信息,有人将能够为您找到基于集合的解决方案。

于 2011-03-25T11:14:13.787 回答
1

可能在某些情况下可以使用Tally 表。它可能是循环和游标的一个很好的替代方案,但请记住它不能适用于所有情况。可以在这里找到一个很好的解释案例

于 2015-01-09T17:36:32.567 回答