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)的一般替代品是什么?
没有“一般替换”——你把所有的“脏工作”都藏在这里了,所以很难判断在这种情况下是否有特定的替换。在某些特定情况下,您一次处理一组行,无论是使用游标、while 循环还是任何其他迭代过程,都可以转换为基于集合的过程来处理所有行一次就好多了。但是还有其他事情必须一次完成一行,例如执行存储过程或每行一些动态 SQL,跨多个数据库的相同查询等。
无论是否使用游标,无论您使用声明游标还是其他一些循环结构(请参阅此帖子),您暗指和链接的问题都是相同的,并且当您必须做的事情必须在一行中完成时无关紧要反正时间。因此,如果您提供有关此光标正在执行的操作的一些特定详细信息,您可能会得到一些有关如何删除光标(或您不能删除)的建议,但是您正在寻找一种可以应用的神奇的消除所有光标的方法在所有情况下都会让您感到非常沮丧。
对于进入该语言的新人的一般建议,恕我直言,应该始终考虑您需要对一组行做什么,而不是您需要对一组中的每一行做什么。语言上的差异是微妙的,但至关重要。如果人们将问题视为一组数据而不是一堆单独的行,那么默认情况下他们可能不太可能使用游标。但是,如果它们来自不同类型的编程 - 其中迭代是最好/唯一的方式 - 除了简单地教他们 SQL Server 没有针对这种方式进行优化之外,我不知道有什么方法可以使这一点变得明显或自动的。
您的问题仍然要求一般替换,我仍然相信没有这样的事情。
处理一个或多个游标的能力取决于将在该游标内执行的内容。不知道里面发生了什么,就没有办法说出来。可能没有解决方法,您必须逐行处理。
下面是一些例子。
此示例是最基本的示例,只是您可以一次查询整个数据集或部分数据集的事实,但已创建游标并逐行查询数据。常用的替换它的是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)
这里真正的解决方法是弄清楚为什么以这种方式呈现数据,并更改应用程序/......以不需要这种格式的数据,将其存储在某处......
如果你的手被绑住了,这将是下一个最好的事情。
数据
创建表 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)
有时别无选择
如果您不能在集合中工作,并且必须逐行处理,您仍然可以优化游标。
加速光标的最大变化之一是添加LOCAL FAST_FORWARD
到它。
DECLARE C CURSOR LOCAL FAST_FORWARD FOR SELECT LotrId from dbo.Lotr
Run Code Online (Sandbox Code Playgroud)
看看我这篇文章通过@AaronBertrand他使用或不使用诸如光标设置时,解释了在性能上可能存在的差异LOCAL
和FAST_FORWARD
。