新手的光标替换

und*_*ica 6 sql-server cursors

我想知道游标的一般替换是什么。我看到的游标的一般实现是

DECLARE @variable INT, @sqlstr NVARCHAR(MAX)

DECLARE cursor_name CURSOR
FOR select_statement --essentially to get an array for @variable 
                     --usually it's a subset of unique ids for accounts, clients, parts, etc

OPEN cursor_name
FETCH NEXT FROM cursor_name INTO @variable
WHILE @@FETCH_STATUS = 0
BEGIN
     SET @sqlstr = N'
     /* some query that uses '+ str(@variable) +' to do dirty work
     such as: go through all our accounts, if it''s some subset (possible new cursor), 
     go through those accounts and connect this way, 
     map those fields and add it to our big uniform table */
     '

     EXEC sp_executesql @sqlstr
FETCH NEXT FROM cursor_name INTO @variable
END

CLOSE cursor_name
DEALLOCATE cursor_name
Run Code Online (Sandbox Code Playgroud)

既然这么多人都反对游标(向SO点头致意:为什么人们讨厌游标)一般实现(最好是 SQL Server)的一般替代品是什么?

Aar*_*and 6

没有“一般替换”——你把所有的“脏工作”都藏在这里了,所以很难判断在这种情况下是否有特定的替换。在某些特定情况下,您一次处理一组行,无论是使用游标、while 循环还是任何其他迭代过程,都可以转换为基于集合的过程来处理所有行一次就好多了。但是还有其他事情必须一次完成一行,例如执行存储过程或每行一些动态 SQL,跨多个数据库的相同查询等。

无论是否使用游标,无论您使用声明游标还是其他一些循环结构(请参阅此帖子),您暗指和链接的问题都是相同的,并且当您必须做的事情必须在一行中完成时无关紧要反正时间。因此,如果您提供有关此光标正在执行的操作的一些特定详细信息,您可能会得到一些有关如何删除光标(或您不能删除)的建议,但是您正在寻找一种可以应用的神奇的消除所有光标的方法在所有情况下都会让您感到非常沮丧。

对于进入该语言的新人的一般建议,恕我直言,应该始终考虑您需要对一组行做什么,而不是您需要对一组中的每一行做什么。语言上的差异是微妙的,但至关重要。如果人们将问题视为一组数据而不是一堆单独的行,那么默认情况下他们可能不太可能使用游标。但是,如果它们来自不同类型的编程 - 其中迭代是最好/唯一的方式 - 除了简单地教他们 SQL Server 没有针对这种方式进行优化之外,我不知道有什么方法可以使这一点变得明显或自动的。

您的问题仍然要求一般替换,我仍然相信没有这样的事情。


Ran*_*gen 6

这取决于™

处理一个或多个游标的能力取决于将在该游标内执行的内容。不知道里面发生了什么,就没有办法说出来。可能没有解决方法,您必须逐行处理。

下面是一些例子。

不成套工作

此示例是最基本的示例,只是您可以一次查询整个数据集或部分数据集的事实,但已创建游标并逐行查询数据。常用的替换它的是JOIN's、CROSS APPLY/OUTER APPLY和其他。

考虑以下数据集:

CREATE TABLE dbo.Lotr(LotrId int, CharacterName varchar(255), Val varchar(255));
CREATE TABLE dbo.LotrAttributes(LotrATtributeId int, LotrId int, AttrVal varchar(255));

INSERT INTO dbo.Lotr(LotrId,CharacterName,Val)
VALUES(1,'Frodo','Ring')
,(2,'Gandalf','Staff');

INSERT INTO dbo.LotrAttributes(LotrId,LotrATtributeId,AttrVal)
VALUES(1,1,'RingAttribute1')
,(1,2,'RingAttribute2')
,(1,3,'RingAttribute3')
,(2,4,'StaffAttribute1')
,(2,5,'StaffAttribute2');
Run Code Online (Sandbox Code Playgroud)

人们可以尝试通过循环遍历Lotr表来找到每条记录并分别匹配。

光标:

DECLARE @LotrID int
DECLARE C CURSOR FOR SELECT LotrId from dbo.Lotr;
OPEN C
FETCH NEXT FROM C INTO @LotrID;
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT LotrATtributeId from dbo.LotrAttributes where LotrId = @LotrID;
FETCH NEXT FROM C INTO @LotrID;
END
CLOSE C
DEALLOCATE C
Run Code Online (Sandbox Code Playgroud)

产生两个结果集

LotrATtributeId
1
2
3
LotrATtributeId
4
5
Run Code Online (Sandbox Code Playgroud)

inner join使用它时,我们得到与一个结果集相同的结果。

SELECT LotrATtributeId from dbo.Lotr L
INNER JOIN dbo.LotrAttributes LA 
ON L.LotrId = LA.LotrId;

LotrATtributeId
1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

字符串操作

一个常见的方法是用于FOR XML PATH('')替换游标内的字符串操作。

数据集

CREATE TABLE dbo.Lotr(LotrId int, CharacterName varchar(255), Val varchar(255));
CREATE TABLE dbo.LotrAttributes(LotrATtributeId int, LotrId int, AttrVal varchar(255));

INSERT INTO dbo.Lotr(LotrId,CharacterName,Val)
VALUES(1,'Frodo','Ring');

INSERT INTO dbo.LotrAttributes(LotrId,LotrATtributeId,AttrVal)
VALUES(1,1,'RingAttribute1')
,(1,2,'RingAttribute2')
,(1,3,'RingAttribute3');
Run Code Online (Sandbox Code Playgroud)

带字符串操作的双光标

DECLARE @LotrId int, @CharacterName varchar(255), @Val varchar(255)
DECLARE @LotrATtributeId int, @AttrVal varchar(255)
DECLARE C CURSOR FOR
SELECT LotrId,CharacterName, Val FROM dbo.Lotr
OPEN C
FETCH NEXT FROM C INTO @LotrId,@CharacterName,@Val
WHILE @@FETCH_STATUS = 0
BEGIN

        SET @CharacterName +='|'+ @Val

        DECLARE D CURSOR FOR
        SELECT LotrATtributeId, AttrVal FROM dbo.LotrAttributes where LotrId = @LotrId
        OPEN D
        FETCH NEXT FROM D INTO @LotrATtributeId,@AttrVal
        WHILE @@FETCH_STATUS = 0
        BEGIN
        SET @CharacterName +='['+@AttrVal+ '],'

        FETCH NEXT FROM D INTO @LotrATtributeId,@AttrVal
        END
        CLOSE D 
        DEALLOCATE D

FETCH NEXT FROM C INTO @LotrId,@CharacterName,@Val
END
CLOSE C
DEALLOCATE C
SELECT LEFT(@CharacterName,len(@charactername)-1);
Run Code Online (Sandbox Code Playgroud)

结果

(No column name)
Frodo|Ring[RingAttribute1],[RingAttribute2],[RingAttribute3],
Run Code Online (Sandbox Code Playgroud)

使用 FOR XML PATH('') 删除游标

SELECT L.Charactername +'|'+ L.Val + (SELECT stuff((SELECT ','+QUOTENAME(AttrVal) FROM dbo.LotrAttributes LA WHERE LA.LotrId = L.LotrId FOR XML PATH('')), 1, 1, ''))
FROM
dbo.Lotr L;
Run Code Online (Sandbox Code Playgroud)

*

这里真正的解决方法是弄清楚为什么以这种方式呈现数据,并更改应用程序/......以不需要这种格式的数据,将其存储在某处......

如果你的手被绑住了,这将是下一个最好的事情。


根据另一个表中的 Id 将前 10 个值插入到临时表中

数据

创建表 dbo.sometable(InsertTableId int, val varchar(255)); 创建表 dbo.Top10Table(Top10TableId int, InsertTableId int, val varchar(255));

INSERT INTO dbo.sometable(InsertTableId,val)
VALUES(1,'bla')
,(2,'blabla');
INSERT INTO dbo.Top10Table(Top10TableId,InsertTableId,Val)
VALUES(1,1,'WUW')
,(2,1,'WUW')
,(3,1,'WUW');
Run Code Online (Sandbox Code Playgroud)

光标

CREATE TABLE #Top10Values(Top10TableId int, InsertTableId int, val varchar(255))

    DECLARE @InsertTableId int;
    DECLARE C CURSOR FOR select InsertTableId from dbo.sometable;
    OPEN C
    FETCH NEXT FROM C INTO @InsertTableId;
    WHILE @@FETCH_STATUS =0
    BEGIN
    INSERT INTO #Top10Values(Top10TableId,InsertTableId,val)
    SELECT top(10) Top10TableId,InsertTableId,Val FROM dbo.Top10Table 
    where InsertTableId = @InsertTableId
    ORDER BY Top10TableId 

    FETCH NEXT FROM C INTO @InsertTableId;
    END
    CLOSE C
    DEALLOCATE C

    SELECT * FROM  #Top10Values;
    DROP TABLE #Top10Values;
Run Code Online (Sandbox Code Playgroud)

结果

Top10TableId    InsertTableId   val
1   1   WUW
2   1   WUW
3   1   WUW
Run Code Online (Sandbox Code Playgroud)

CROSS APPLY和替换光标CTE

CREATE TABLE #Top10Values(Top10TableId int, InsertTableId int, val varchar(255));
;WITH CTE 
AS
(
select InsertTableId  from dbo.sometable
)

INSERT INTO #Top10Values(Top10TableId,InsertTableId,val)
SELECT  T1T.Top10TableId,T1T.InsertTableId,T1T.Val 
FROM 
CTE
CROSS APPLY (SELECT TOP (10) Top10TableId,InsertTableId,Val from dbo.Top10Table T1T
WHERE T1T.InsertTableId = CTE.InsertTableId
) T1T ;

SELECT * FROM  #Top10Values;
DROP TABLE #Top10Values;
Run Code Online (Sandbox Code Playgroud)

其他例子

  • 使用CROSS APPLY 此处替换光标以选择每个供应商的动态项目集的示例。
  • 此处使用窗口函数替换光标的示例。

有时别无选择

如果您不能在集合中工作,并且必须逐行处理,您仍然可以优化游标。

加速光标的最大变化之一是添加LOCAL FAST_FORWARD到它。

DECLARE C CURSOR LOCAL FAST_FORWARD FOR SELECT LotrId from dbo.Lotr
Run Code Online (Sandbox Code Playgroud)

看看我这篇文章通过@AaronBertrand他使用或不使用诸如光标设置时,解释了在性能上可能存在的差异LOCALFAST_FORWARD