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

时间:2009-05-20 05:30:32

标签: sql sql-server stored-procedures

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

我该怎么写这个查询?

7 个答案:

答案 0 :(得分:226)

使用光标

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

在MS SQL中,here's an example article

请注意,游标比基于集合的操作慢,但比手动while循环更快;更多详情in this SO question

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

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

答案 1 :(得分:53)

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

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

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

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

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

答案 2 :(得分:9)

您的表和字段名称需要类似这样的替换。

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

答案 3 :(得分:7)

您可以使用动态查询来执行此操作。

extension String {

    var RFC3986UnreservedEncoded:String {
        let unreservedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
        let unreservedCharsSet: CharacterSet = CharacterSet(charactersIn: unreservedChars)
        let encodedString: String = self.addingPercentEncoding(withAllowedCharacters: unreservedCharsSet)!
        return encodedString
    }
}

答案 4 :(得分:6)

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

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

其中udfMyFunction是您创建的一个函数,它接收用户ID并执行您需要执行的任何操作。

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

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

(当然,我的答案预先假定您只对获取SP的输出感兴趣并且您没有更改实际数据。我发现“以某种方式改变用户数据”有点模糊不清原来的问题,所以我认为我会把它作为一种可能的解决方案。完全取决于你在做什么!)

答案 5 :(得分:5)

使用表变量或临时表。

如前所述,光标是最后的手段。主要是因为它使用了大量资源,发出了锁定问题,并且可能是您不能理解如何正确使用SQL的标志。

  

旁注:我曾经遇到过使用游标进行更新的解决方案   表中的行。经过一番审查后,事实证明了这一切   可以用单个UPDATE命令替换。但是,在这种情况下,   应该执行存储过程的单个SQL命令   不会工作。

创建一个这样的表变量(如果您正在处理大量数据或内存不足,请使用temporary table代替):

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;

答案 6 :(得分:2)

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

但是对于我在Azure SQL上的需求以及查询中的“distinct”,我不得不像这样修改代码:

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;

我希望这有助于某人...