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

Met*_*uru 197 sql sql-server stored-procedures

我有一个以某种方式改变用户数据的存储过程.我传递给它user_id,它确实是这样的.我想在一个表上运行查询,然后对于每个user_id,我发现在该user_id上运行一次存储过程

我该怎么写这个查询?

Ste*_*owe 235

使用光标

ADDENDUM:[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
Run Code Online (Sandbox Code Playgroud)

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

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

附录2:如果您要处理的不仅仅是几条记录,请先将它们拉入临时表,然后将光标放在临时表上; 这将阻止SQL升级到表锁并加快操作

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

  • 在声明之后你错过了'open cur' - 这给了我'光标未打开'的错误.我没有代表进行编辑. (20认同)
  • 游标仅作为最后的度假村!! 忽略这个建议并承受后果! (12认同)
  • 你可以通过对他们的评论进行投票来感谢他们.谁知道,也许这样他们下次就会让代表进行编辑!:-) (5认同)
  • 感谢提醒使用临时表避免长时间执行导致的潜在锁定问题。 (2认同)

KM.*_*KM. 54

如果你需要循环,尝试改变你的方法!

在父存储过程中,创建一个包含您需要处理的数据的#temp表.调用子存储过程,#temp表将是可见的,您可以处理它,希望使用整个数据集并且无需游标或循环.

这实际上取决于这个子存储过程正在做什么.如果您正在更新,您可以"加入"加入#temp表并在一个语句中完成所有工作而无需循环.INSERT和DELETE也可以这样做.如果需要使用IF进行多次更新,可以UPDATE FROM使用#temp表将这些更新转换为多个并使用CASE语句或WHERE条件.

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

发布你希望在循环中调用的这个过程的内容,我打赌10次中的9次,你可以把它写在一组行上.

  • 基于集合的操作总是更可取的.但是,请记住,修改SP并不总是一种选择 - 请考虑供应商提供的解决方案.有些用户甚至可能没有可见性,只留下光标或循环选项.在我的商店里,我们的开发人员可以看到所有内容,但是由于触发器,嵌套过程,操作的记录数等等,如果在供应商的应用程序之外构建解决方案,有很多障碍要清除.很多时候他们的最佳选择,由于应用程序的复杂性,就是简单地通过记录光标. (7认同)
  • 如果可以的话,我会给你一百个赞成票 (4认同)
  • +1表示非常好的解决方法,假设你控制了子程序 (3认同)

u07*_*7ch 10

您的表和字段名称将需要类似此替换的内容.

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
Run Code Online (Sandbox Code Playgroud)

  • while循环比游标慢 (2认同)

Dav*_*con 9

您可以使用动态查询来完成此操作.

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);
Run Code Online (Sandbox Code Playgroud)


ran*_*nce 6

是否可以使用用户定义的函数来复制存储过程正在执行的操作?

SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition
Run Code Online (Sandbox Code Playgroud)

其中udfMyFunction是一个函数,它接收用户ID并执行您需要做的任何事情.

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

我同意在可能的情况下应该尽可能避免使用游标.它通常是可能的!

(当然,我的答案预先假定你只对获取SP的输出感兴趣并且你没有改变实际数据.我发现"以某种方式改变用户数据"与原始问题有点模棱两可,所以我认为我会提供这个可能的解决方案.完全取决于你在做什么!)

  • OP:“以某种方式改变用户数据的存储过程”[MSDN](http://technet.microsoft.com/en-us/library/ms191320.aspx):用户定义的函数不能用于执行修改数据库状态。但是 SQLSVR 2014 似乎没有问题 (2认同)

Erk*_*Erk 6

使用表变量或临时表。

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

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

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

DECLARE @menus AS TABLE (
    id INT IDENTITY(1,1),
    parent NVARCHAR(128),
    child NVARCHAR(128));
Run Code Online (Sandbox Code Playgroud)

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');
Run Code Online (Sandbox Code Playgroud)

声明一些变量:

DECLARE @id INT = 1;
DECLARE @parentName NVARCHAR(128);
DECLARE @childName NVARCHAR(128);
Run Code Online (Sandbox Code Playgroud)

最后,对表中的数据创建一个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
Run Code Online (Sandbox Code Playgroud)

第一个选择从临时表中获取数据。第二个选择将更新@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;
Run Code Online (Sandbox Code Playgroud)