不使用游标的每个行的SQL调用存储过程

Joh*_*lph 147 sql sql-server stored-procedures cursor

如何为表中的每一行调用存储过程,其中行的列是sp的输入参数而不使用Cursor?

Mar*_*ell 185

一般来说,我总是寻找基于集合的方法(有时以牺牲更改架构为代价).

但是,这个片段确实有它的位置..

-- Declare & init (2008 syntax)
DECLARE @CustomerID INT = 0

-- Iterate over all customers
WHILE (1 = 1) 
BEGIN  

  -- Get next customerId
  SELECT TOP 1 @CustomerID = CustomerID
  FROM Sales.Customer
  WHERE CustomerID > @CustomerId 
  ORDER BY CustomerID

  -- Exit loop if no more customers
  IF @@ROWCOUNT = 0 BREAK;

  -- call your sproc
  EXEC dbo.YOURSPROC @CustomerId

END
Run Code Online (Sandbox Code Playgroud)

  • 与接受的答案一样使用CATION:根据您的表格和索引结构,它可能表现很差(O(n ^ 2)),因为您每次枚举时都必须订购并搜索您的表格. (19认同)
  • @@ ROWCOUNT只能读一次.甚至IF/PRINT语句也会将其设置为0. @@ ROWCOUNT的测试必须在选择后"立即"完成.我会重新检查你的代码/环境.http://technet.microsoft.com/en-us/library/ms187316.aspx (7认同)
  • 这似乎不起作用(break永远不会退出循环 - 工作完成但查询在循环中旋转).初始化id并在while条件中检查null退出循环. (3认同)
  • 尽管循环并不比游标好,但要小心,它们甚至会更糟:http://www.techrepublic.com/blog/the-enterprise-cloud/comparing-cursor-vs-while-loop-performance-in-sql -服务器-2008 / (2认同)
  • @Brennan Pope 使用 CURSOR 的 LOCAL 选项,它将在失败时被销毁。使用 LOCAL FAST_FORWARD ,在此类循环中不使用 CURSOR 的理由几乎为零。它肯定会优于这个 WHILE 循环。 (2认同)

mar*_*c_s 39

您可以执行以下操作:通过例如CustomerID(使用AdventureWorks Sales.Customer示例表)对表进行排序,并使用WHILE循环遍历这些客户:

-- define the last customer ID handled
DECLARE @LastCustomerID INT
SET @LastCustomerID = 0

-- define the customer ID to be handled now
DECLARE @CustomerIDToHandle INT

-- select the next customer to handle    
SELECT TOP 1 @CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > @LastCustomerID
ORDER BY CustomerID

-- as long as we have customers......    
WHILE @CustomerIDToHandle IS NOT NULL
BEGIN
    -- call your sproc

    -- set the last customer handled to the one we just handled
    SET @LastCustomerID = @CustomerIDToHandle
    SET @CustomerIDToHandle = NULL

    -- select the next customer to handle    
    SELECT TOP 1 @CustomerIDToHandle = CustomerID
    FROM Sales.Customer
    WHERE CustomerID > @LastCustomerID
    ORDER BY CustomerID
END
Run Code Online (Sandbox Code Playgroud)

这应该适用于任何表,只要您可以ORDER BY在某些列上定义某种类型.

  • 基于集合的实现是否可行? (6认同)
  • 再次:丹尼尔.函数是,存储过程没有.根据定义,存储过程可能具有副作用,并且查询中不允许副作用.类似地,功能语言中的适当"地图"禁止副作用. (4认同)
  • @marc_s为集合中的每个项执行一个函数/ storeprocedure,这听起来像基于集合的操作的面包和黄油.问题可能来自于没有每个人的结果.请参阅大多数函数式编程语言中的"map". (2认同)

小智 28

DECLARE @SQL varchar(max)=''

-- MyTable has fields fld1 & fld2

Select @SQL = @SQL + 'exec myproc ' + convert(varchar(10),fld1) + ',' 
                   + convert(varchar(10),fld2) + ';'
From MyTable

EXEC (@SQL)
Run Code Online (Sandbox Code Playgroud)

好的,所以我永远不会将这些代码投入生产,但它确实满足了您的要求.


Max*_*xxx 10

Marc的答案很好(如果我能弄明白的话,我会评论它!)
我想我会指出改变循环所以SELECT只存在一次(在我需要的实际情况中)这样做,SELECT非常复杂,写两次是一个有风险的维护问题).

-- define the last customer ID handled
DECLARE @LastCustomerID INT
SET @LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE @CustomerIDToHandle INT
SET @CustomerIDToHandle = 1

-- as long as we have customers......    
WHILE @LastCustomerID <> @CustomerIDToHandle
BEGIN  
  SET @LastCustomerId = @CustomerIDToHandle
  -- select the next customer to handle    
  SELECT TOP 1 @CustomerIDToHandle = CustomerID
  FROM Sales.Customer
  WHERE CustomerID > @LastCustomerId 
  ORDER BY CustomerID

  IF @CustomerIDToHandle <> @LastCustomerID
  BEGIN
      -- call your sproc
  END

END
Run Code Online (Sandbox Code Playgroud)


Dav*_*ths 7

如果您可以将存储过程转换为返回表的函数,则可以使用交叉应用.

例如,假设您有一个客户表,并且您想要计算其订单的总和,您将创建一个带有CustomerID并返回总和的函数.

你可以这样做:

SELECT CustomerID, CustomerSum.Total

FROM Customers
CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum
Run Code Online (Sandbox Code Playgroud)

函数的位置如下:

CREATE FUNCTION ComputeCustomerTotal
(
    @CustomerID INT
)
RETURNS TABLE
AS
RETURN
(
    SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = @CustomerID
)
Run Code Online (Sandbox Code Playgroud)

显然,上面的示例可以在单个查询中没有用户定义的函数的情况下完成.

缺点是函数非常有限 - 存储过程的许多特性在用户定义的函数中不可用,并且将存储过程转换为函数并不总是有效.


Mit*_*eat 6

对于SQL Server 2005以上版本,您可以使用CROSS APPLY和表值函数执行此操作.

为了清楚起见,我指的是存储过程可以转换为表值函数的情况.

  • 好主意,但功能无法调用存储过程 (12认同)

AjV*_*Jsy 6

我会使用可接受的答案,但是另一种可能性是使用表变量来保存一组编号的值(在这种情况下,仅是表的ID字段),并通过具有JOIN的行号遍历那些值,以达到检索循环中所需的操作。

DECLARE @RowCnt int; SET @RowCnt = 0 -- Loop Counter

-- Use a table variable to hold numbered rows containg MyTable's ID values
DECLARE @tblLoop TABLE (RowNum int IDENTITY (1, 1) Primary key NOT NULL,
     ID INT )
INSERT INTO @tblLoop (ID)  SELECT ID FROM MyTable

  -- Vars to use within the loop
  DECLARE @Code NVarChar(10); DECLARE @Name NVarChar(100);

WHILE @RowCnt < (SELECT COUNT(RowNum) FROM @tblLoop)
BEGIN
    SET @RowCnt = @RowCnt + 1
    -- Do what you want here with the data stored in tblLoop for the given RowNum
    SELECT @Code=Code, @Name=LongName
      FROM MyTable INNER JOIN @tblLoop tL on MyTable.ID=tL.ID
      WHERE tl.RowNum=@RowCnt
    PRINT Convert(NVarChar(10),@RowCnt) +' '+ @Code +' '+ @Name
END
Run Code Online (Sandbox Code Playgroud)

  • 这更好,因为它不会假设您所追求的值是整数或可以进行合理比较。 (2认同)

Adr*_*eer 5

这是已提供答案的变体,但性能应该更好,因为它不需要 ORDER BY、COUNT 或 MIN/MAX。这种方法的唯一缺点是您必须创建一个临时表来保存所有 Id(假设您的 CustomerID 列表中有间隙)。

也就是说,我同意@Mark Powell 的观点,尽管一般来说,基于集合的方法应该仍然更好。

DECLARE @tmp table (Id INT IDENTITY(1,1) PRIMARY KEY NOT NULL, CustomerID INT NOT NULL)
DECLARE @CustomerId INT 
DECLARE @Id INT = 0

INSERT INTO @tmp SELECT CustomerId FROM Sales.Customer

WHILE (1=1)
BEGIN
    SELECT @CustomerId = CustomerId, @Id = Id
    FROM @tmp
    WHERE Id = @Id + 1

    IF @@rowcount = 0 BREAK;

    -- call your sproc
    EXEC dbo.YOURSPROC @CustomerId;
END
Run Code Online (Sandbox Code Playgroud)