如何为查询返回的每一行执行一次存储过程?


206

我有一个存储过程,以某种方式更改用户数据。我将它传递给user_id,它就是这样做的。我想在一个表上运行查询,然后为每个user_id查找在该user_id上运行一次存储过程

我将如何为此编写查询?


5
你需要指定哪些RDBMS -答案是不同的SQL Server,Oracle,MySQL等,等
Gary.Ray

5
可能您根本不需要存储过程。您能准确概述存储过程的“作用”吗?也许整个过程可以表示为单个更新语句。如果可能,通常应避免“为每个记录执行一次”模式。
Tomalak

您正在使用哪个数据库?
SO用户

1
您应该阅读这篇文章...项目2说不要使用游标codeproject.com/KB/database/sqldodont.aspx ...记住我也反对过早的优化。
Michael Prewecki,2009年

7
@MichaelPrewecki:如果您在那篇写得不好的文章中进一步阅读,您会看到第10项是“除非您知道自己在做什么,否则请不要使用服务器端游标”。我认为这是“我知道我在做什么”的情况。
加布

Answers:


246

使用游标

附录:[MS SQL游标示例]

declare @field1 int
declare @field2 int
declare cur CURSOR LOCAL for
    select field1, field2 from sometable where someotherfield is null

open cur

fetch next from cur into @field1, @field2

while @@FETCH_STATUS = 0 BEGIN

    --execute your sproc on each row
    exec uspYourSproc @field1, @field2

    fetch next from cur into @field1, @field2
END

close cur
deallocate cur

在MS SQL中,这是一个示例文章

请注意,游标比基于集合的操作慢,但比手动while循环快;这个问题的更多细节

附录2:如果您要处理的不仅仅是几个记录,请先将它们拉到临时表中,然后将光标移到临时表上;否则,请执行以下操作:这将防止SQL升级为表锁并加快操作速度

附录3:当然,如果您可以内联存储过程对每个用户ID进行的操作,并将整个操作作为单个SQL更新语句运行,那将是最佳选择


21
您在声明后错过了“ open cur”-这是给我“ cursor is not open”的错误。我没有销售代表来进行编辑。
Fiona-myaccessible.website

5
您可以通过对他们的评论进行投票来感谢人们。谁知道呢,也许那样他们下次就会有代表来进行编辑!:-)
Robino 2014年

确保在存储过程中使用的字段中检查JOINS和WHERE子句的索引。添加适当的索引后,我极大地加快了在循环中调用SP的速度。
马修

1
感谢您提醒使用临时表,以避免由于长时间执行而导致的潜在锁定问题。
托尼

有时,存储过程太大或太复杂而无法内联而不冒引入错误的风险。在性能不是最终优先事项的情况下,在游标循环中执行SP通常是最实际的选择。
Suncat2000

55

如果需要循环,请尝试更改您的方法!

在父存储过程中,创建一个#temp表,其中包含您需要处理的数据。调用子存储过程,#temp表将可见,您可以对其进行处理,希望可以处理整个数据集,并且没有游标或循环。

这实际上取决于此子存储过程的功能。如果您要进行UPDATE,则可以在#temp表中“更新自”联接,并在一条语句中完成所有工作而无需循环。可以对INSERT和DELETE执行相同的操作。如果您需要对IF进行多次更新,则可以UPDATE FROM使用#temp表将其转换为多个,并使用CASE语句或WHERE条件。

当在数据库中工作时,尝试丢失循环的思维方式,这是真正的性能消耗,将导致锁定/阻塞并减慢处理速度。如果到处循环,您的系统将无法很好地扩展,并且当用户开始抱怨刷新缓慢时,将很难加速。

将您要调用的该过程的内容循环发布,我打赌10次中有9次,您可以编写它以处理一组行。


3
+1是一个很好的解决方法,假设您控制孩子的存储过程
Steven A. Lowe

有点想法,这种解决方案到目前为止是优越的!
encc

7
基于集合的操作始终是首选。但是,请记住,修改SP并非总是一种选择-考虑供应商提供的解决方案。有些用户甚至没有可见性,只留下了光标或循环选项。在我的商店中,我们的开发人员可以看到所有内容,但是由于触发器,嵌套的proc,正在处理的记录数等原因,是否有很多障碍需要清除是否在供应商的应用程序之外构建了解决方案。应用程序的复杂性,在于简单地游历记录。
史蒂夫·曼吉阿米里'17

11

表格和字段名称将需要类似的替换。

Declare @TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare @i Int, @MaxI Int, @UserID nVarchar(50)

Insert into @TableUser
Select User_ID
From Users 
Where (My Criteria)
Select @MaxI = @@RowCount, @i = 1

While @i <= @MaxI
Begin
Select @UserID = UserID from @TableUsers Where MyRowCount = @i
Exec prMyStoredProc @UserID
Select

 @i = @i + 1, @UserID = null
End

2
while循环比游标慢
Steven A. Lowe

不支持声明游标SQL构造或语句(??)
MetaGuru

9

您可以通过动态查询来完成。

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);

6

用户定义的函数无法复制存储过程正在执行的操作吗?

SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition

其中udfMyFunction是您制作的函数,它接受用户ID并执行您需要执行的任何操作。

参见http://www.sqlteam.com/article/user-defined-functions更多背景信息,

我同意,应该尽可能避免使用游标。通常这是可能的!

(当然,我的回答是假设您只对从SP获得输出感兴趣,并且您没有更改实际数据。我发现“以某种方式更改用户数据”与原始问题有些歧义,因此以为我会提供此解决方案。完全取决于您的工作!)


1
OP:“以某种方式更改用户数据的存储过程” MSDN:用户定义的函数不能用于执行修改数据库状态的操作。但是,SQLSVR 2014似乎没有问题
johnny

6

使用表变量或临时表。

如前所述,游标是不得已的方法。通常是因为它使用大量资源,发出问题锁,并且可能只是您不了解如何正确使用SQL的信号。

旁注:我曾经遇到一种使用游标更新表中行的解决方案。经过仔细检查,结果整个事情可以用一个UPDATE命令代替。但是,在这种情况下,应执行存储过程,则单个SQL命令将不起作用。

创建一个这样的表变量(如果您要处理大量数据或内存不足,请改用临时表):

DECLARE @menus AS TABLE (
    id INT IDENTITY(1,1),
    parent NVARCHAR(128),
    child NVARCHAR(128));

id是很重要的。

替换parentchild一些好的数据,如相关的标识符或整个数据集进行操作的。

在表中插入数据,例如:

INSERT INTO @menus (parent, child) 
  VALUES ('Some name',  'Child name');
...
INSERT INTO @menus (parent,child) 
  VALUES ('Some other name', 'Some other child name');

声明一些变量:

DECLARE @id INT = 1;
DECLARE @parentName NVARCHAR(128);
DECLARE @childName NVARCHAR(128);

最后,对表中的数据创建一个while循环:

WHILE @id IS NOT NULL
BEGIN
    SELECT @parentName = parent,
           @childName = child 
        FROM @menus WHERE id = @id;

    EXEC myProcedure @parent=@parentName, @child=@childName;

    SELECT @id = MIN(id) FROM @menus WHERE id > @id;
END

第一个选择从临时表中获取数据。第二个选择将更新@id。MIN如果未选择任何行,则返回null。

另一种方法是在表有行时循环,SELECT TOP 1然后从临时表中删除选定的行:

WHILE EXISTS(SELECT 1 FROM @menuIDs) 
BEGIN
    SELECT TOP 1 @menuID = menuID FROM @menuIDs;

    EXEC myProcedure @menuID=@menuID;

    DELETE FROM @menuIDs WHERE menuID = @menuID;
END;

3

我喜欢Dave Rincon的动态查询方式,因为它不使用游标,而且又小又容易。谢谢戴夫的分享。

但是对于我对Azure SQL的需求以及查询中的“与众不同”,我不得不修改如下代码:

Declare @SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines 
SELECT @SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' + 
              convert(varchar(8),tenantid) + ';' 
              from Dim_Machine
              where iscurrent = 1
              FOR XML PATH(''))

--for debugging print the sql 
print @SQL;

--execute the generated sql script
exec sp_executesql @SQL;

我希望这可以帮助别人...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.