代码之家  ›  专栏  ›  技术社区  ›  Johannes Rudolph

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

  •  138
  • Johannes Rudolph  · 技术社区  · 16 年前

    当一行的列是sp的输入参数时,如何为表中的每一行调用存储过程 使用光标?

    16 回复  |  直到 11 年前
        1
  •  219
  •   BenMorel Manish Pradhan    12 年前

    一般来说,我总是寻找一种基于集合的方法(有时以改变模式为代价)。

    然而,这个片段确实有它的位置。。

    -- 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
    
        2
  •  40
  •   Tim Cooper    14 年前

    您可以这样做:通过CustomerID(使用AdventureWorks)来订购您的表 Sales.Customer

    -- 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
    

    只要可以定义某种类型的 ORDER BY 在某个专栏上。

        3
  •  30
  •   Anthony Horne Eelke    9 年前
    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)
    

    好的,所以我永远不会把这样的代码投入生产,但它确实满足了您的需求。

        4
  •  14
  •   AjV Jsy    11 年前

    我会使用可接受的答案,但另一种可能是使用表变量来保存一组编号的值(在本例中只是表的ID字段),并通过与表的连接按行编号循环这些值,以检索循环中操作所需的任何内容。

    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
    
        5
  •  11
  •   AjV Jsy    11 年前

    马克的回答很好(如果我能想出如何做的话,我会对此发表评论!)
    我只是想指出,改变循环可能更好,这样 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
    
        6
  •  7
  •   Tim Cooper    14 年前

    例如,假设您有一个客户表,并且您想要计算他们订单的总和,您将创建一个函数,该函数接受CustomerID并返回总和。

    SELECT CustomerID, CustomerSum.Total
    
    FROM Customers
    CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum
    

    其中函数的外观如下所示:

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

    显然,上述示例可以在单个查询中不使用用户定义的函数来完成。

    缺点是函数非常有限—存储过程的许多功能在用户定义的函数中不可用,并且将存储过程转换为函数并不总是可行的。

        7
  •  6
  •   Mitch Wheat    13 年前

    对于SQL Server 2005以后的版本,您可以使用 CROSS APPLY 和一个表值函数。

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

        8
  •  4
  •   Adriaan de Beer    10 年前

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

    尽管如此,我同意马克·鲍威尔的观点,但总的来说,基于集合的方法应该更好。

    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
    
        9
  •  3
  •   beruic BodzioM    13 年前

    -- Declare & init
    DECLARE @CustomerID INT = (SELECT MIN(CustomerID) FROM Sales.Customer); -- First ID
    DECLARE @Data1 VARCHAR(200);
    DECLARE @Data2 VARCHAR(200);
    
    -- Iterate over all customers
    WHILE @CustomerID IS NOT NULL
    BEGIN  
    
      -- Get data based on ID
      SELECT @Data1 = Data1, @Data2 = Data2
        FROM Sales.Customer
        WHERE [ID] = @CustomerID ;
    
      -- call your sproc
      EXEC dbo.YOURSPROC @Data1, @Data2
    
      -- Get next customerId
      SELECT @CustomerID = MIN(CustomerID)
        FROM Sales.Customer
        WHERE CustomerID > @CustomerId 
    
    END
    

    我对一些需要查看的varchar使用这种方法,首先将它们放在临时表中,给它们一个ID。

        10
  •  2
  •   Dani    16 年前

    如果您不知道如何使用游标,我认为您必须在外部执行(获取表,然后为每个语句运行,每次调用sp)

        11
  •  1
  •   Jonas Stensved    15 年前

    1. 使用SQL Management Studio选择数据集中的所有存储过程参数
    2. 右键单击->复制
    3. 粘贴到excel
    4. 将excel语句列表复制到SQL Management Studio中的新查询中并执行。
    5. 完成。

    (在更大的数据集上,我会使用上面提到的解决方案之一)。

        12
  •  1
  •   Dave Hogan    13 年前

    CREATE PROCEDURE setFakeUsers (OUT output VARCHAR(100))
    BEGIN
    
        -- define the last customer ID handled
        DECLARE LastGameID INT;
        DECLARE CurrentGameID INT;
        DECLARE userID INT;
    
        SET @LastGameID = 0; 
    
        -- define the customer ID to be handled now
    
        SET @userID = 0;
    
        -- select the next game to handle    
        SELECT @CurrentGameID = id
        FROM online_games
        WHERE id > LastGameID
        ORDER BY id LIMIT 0,1;
    
        -- as long as we have customers......    
        WHILE (@CurrentGameID IS NOT NULL) 
        DO
            -- call your sproc
    
            -- set the last customer handled to the one we just handled
            SET @LastGameID = @CurrentGameID;
            SET @CurrentGameID = NULL;
    
            -- select the random bot
            SELECT @userID = userID
            FROM users
            WHERE FIND_IN_SET('bot',baseInfo)
            ORDER BY RAND() LIMIT 0,1;
    
            -- update the game
            UPDATE online_games SET userID = @userID WHERE id = @CurrentGameID;
    
            -- select the next game to handle    
            SELECT @CurrentGameID = id
             FROM online_games
             WHERE id > LastGameID
             ORDER BY id LIMIT 0,1;
        END WHILE;
        SET output = "done";
    END;//
    
    CALL setFakeUsers(@status);
    SELECT @status;
    
        13
  •  1
  •   TheTechGuy    9 年前

    更好的解决办法是

    1. 存储过程的副本/过去代码
    2. 将该代码与要再次运行该代码的表(每行)连接起来

    这是一个干净的表格格式输出。而如果对每一行运行SP,则会为每个迭代获得一个单独的查询结果,这很难看。

        14
  •  0
  •   isxaker    10 年前

    --declare counter
    DECLARE     @CurrentRowNum BIGINT = 0;
    --Iterate over all rows in [DataTable]
    WHILE (1 = 1)
        BEGIN
            --Get next row by number of row
            SELECT TOP 1 @CurrentRowNum = extendedData.RowNum
                        --here also you can store another values
                        --for following usage
                        --@MyVariable = extendedData.Value
            FROM    (
                        SELECT 
                            data.*
                            ,ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RowNum
                        FROM [DataTable] data
                    ) extendedData
            WHERE extendedData.RowNum > @CurrentRowNum
            ORDER BY extendedData.RowNum
    
            --Exit loop if no more rows
            IF @@ROWCOUNT = 0 BREAK;
    
            --call your sproc
            --EXEC dbo.YOURSPROC @MyVariable
        END
    
        15
  •  0
  •   William Egge    9 年前

    我有一些生产代码,一次只能处理20名员工,下面是代码的框架。我只是复制了生产代码并删除了下面的内容。

    ALTER procedure GetEmployees
        @ClientId varchar(50)
    as
    begin
        declare @EEList table (employeeId varchar(50));
        declare @EE20 table (employeeId varchar(50));
    
        insert into @EEList select employeeId from Employee where (ClientId = @ClientId);
    
        -- Do 20 at a time
        while (select count(*) from @EEList) > 0
        BEGIN
          insert into @EE20 select top 20 employeeId from @EEList;
    
          -- Call sp here
    
          delete @EEList where employeeId in (select employeeId from @EE20)
          delete @EE20;
        END;
    
      RETURN
    end
    
        16
  •  0
  •   greg    5 年前

    我遇到了一种情况,需要对结果集(表)执行一系列操作。这些操作都是集合操作,所以这不是问题,但是。。。 我需要在多个地方这样做。因此,将相关部分放入一个表类型,然后填充一个表变量w/每个结果集,这样我就可以调用sp并在每次需要时重复这些操作。

    @约翰内斯没有洞察到他的动机,所以这可能对他有帮助,也可能没有帮助。

    https://codingsight.com/passing-data-table-as-parameter-to-stored-procedures/

    这是设置

        drop type if exists cpRootMapType 
    go 
    
    create type cpRootMapType as Table(
        RootId1 int 
        , RootId2 int
    )
    
    go 
    drop procedure if exists spMapRoot2toRoot1
    go 
    create procedure spMapRoot2toRoot1
    (
    @map cpRootMapType Readonly
    )
    as
    
    update linkTable set root = root1  
    from linktable  lt 
    join @map m on lt.root = root2
    
    update comments set root = root1 
    from comments c 
    join @map m on c.root = root2
    
    --  ever growing list of places this map would need to be applied....
    --  now consolidated into one place 
    

    ... populate #matches
    
    declare @map cpRootMapType 
    insert @map select rootid1, rootid2 from #matches
    exec spMapRoot2toRoot1 @map 
    
        17
  •  -1
  •   Kritner    13 年前

    我喜欢做类似的事情(尽管它仍然非常类似于使用光标)

    [守则]

    -- Table variable to hold list of things that need looping
    DECLARE @holdStuff TABLE ( 
        id INT IDENTITY(1,1) , 
        isIterated BIT DEFAULT 0 , 
        someInt INT ,
        someBool BIT ,
        otherStuff VARCHAR(200)
    )
    
    -- Populate your @holdStuff with... stuff
    INSERT INTO @holdStuff ( 
        someInt ,
        someBool ,
        otherStuff
    )
    SELECT  
        1 , -- someInt - int
        1 , -- someBool - bit
        'I like turtles'  -- otherStuff - varchar(200)
    UNION ALL
    SELECT  
        42 , -- someInt - int
        0 , -- someBool - bit
        'something profound'  -- otherStuff - varchar(200)
    
    -- Loop tracking variables
    DECLARE @tableCount INT
    SET     @tableCount = (SELECT COUNT(1) FROM [@holdStuff])
    
    DECLARE @loopCount INT
    SET     @loopCount = 1
    
    -- While loop variables
    DECLARE @id INT
    DECLARE @someInt INT
    DECLARE @someBool BIT
    DECLARE @otherStuff VARCHAR(200)
    
    -- Loop through item in @holdStuff
    WHILE (@loopCount <= @tableCount)
        BEGIN
    
            -- Increment the loopCount variable
            SET @loopCount = @loopCount + 1
    
            -- Grab the top unprocessed record
            SELECT  TOP 1 
                @id = id ,
                @someInt = someInt ,
                @someBool = someBool ,
                @otherStuff = otherStuff
            FROM    @holdStuff
            WHERE   isIterated = 0
    
            -- Update the grabbed record to be iterated
            UPDATE  @holdAccounts
            SET     isIterated = 1
            WHERE   id = @id
    
            -- Execute your stored procedure
            EXEC someRandomSp @someInt, @someBool, @otherStuff
    
        END
    

    请注意,您没有 您的temp/variable表中的identity或isited列,我只是更喜欢这样做,这样我就不必在迭代循环时从集合中删除top记录。

    推荐文章