如何在 CURSOR 循环内提交?

发布于 2024-09-02 15:13:35 字数 800 浏览 2 评论 0原文

我试图看看是否可以在游标循环中执行更新,并且更新的数据在循环的第二次迭代期间得到反映。

DECLARE cur CURSOR
FOR SELECT [Product], [Customer], [Date], [Event] FROM MyTable
WHERE [Event] IS NULL

OPEN cur
FETCH NEXT INTO @Product, @Customer, @Date, @Event
WHILE @@FETCH_STATUS = 0
BEGIN
  SELECT * FROM MyTable WHERE [Event] = 'No Event' AND [Date] < @DATE
  -- Now I update my Event value to 'No Event' for records whose date is less than @Date
  UPDATE MyTable SET [Event] = 'No Event' WHERE [Product] = @Product AND [Customer] = @Customer AND [Date] < @DATE
   FETCH NEXT INTO @Product, @Customer, @Date, @Event
END
CLOSE cur
DEALLOCATE cur

假设当sql执行时所有记录的Event列都是NULL 在上面的 sql 中,我在游标循环内执行选择来查询 MyTable,其中事件值为“无事件”,但即使我在下一行中进行更新,查询也不会返回任何值。 因此,我在想是否有可能更新表并且更新的数据会反映在游标循环的下一次迭代中。

感谢您的帮助, 贾维德

I am trying to see if its possible to perform Update within a cursor loop and this updated data gets reflected during the second iteration in the loop.

DECLARE cur CURSOR
FOR SELECT [Product], [Customer], [Date], [Event] FROM MyTable
WHERE [Event] IS NULL

OPEN cur
FETCH NEXT INTO @Product, @Customer, @Date, @Event
WHILE @@FETCH_STATUS = 0
BEGIN
  SELECT * FROM MyTable WHERE [Event] = 'No Event' AND [Date] < @DATE
  -- Now I update my Event value to 'No Event' for records whose date is less than @Date
  UPDATE MyTable SET [Event] = 'No Event' WHERE [Product] = @Product AND [Customer] = @Customer AND [Date] < @DATE
   FETCH NEXT INTO @Product, @Customer, @Date, @Event
END
CLOSE cur
DEALLOCATE cur

Assume when the sql executes the Event column is NULL for all records
In the above sql, I am doing a select inside the cursor loop to query MyTable where Event value is 'No Event' but the query returns no value even though I am doing an update in the next line.
So, I am thinking if it is even possible to update a table and the updated data get reflected in the next iteration of the cursor loop.

Thanks for any help,
Javid

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

煮茶煮酒煮时光 2024-09-09 15:13:35

首先,这里不需要光标。类似下面的内容将具有相同的语义(从所有 Event 均为 NULL 的起始位置开始)并且效率更高。

WITH T
     AS (SELECT [Event],
                RANK() OVER (PARTITION BY [Product], [Customer] 
                                 ORDER BY [Date] DESC) AS Rnk
         FROM   MyTable)
UPDATE T
SET    [Event] = 'No Event'
WHERE  Rnk > 1 

其次,关于标题中要在游标循环内提交的问题与其他地方相同。您只需要一个 COMMIT 语句。但是,如果您不在较大的事务中运行此操作,则 UPDATE 语句无论如何都会自动提交。

第三,你真正的问题似乎与提交无关。它与反映后续迭代中数据更新的游标有关。对于问题中的情况,您需要 DYNAMIC 光标

定义一个游标,反映对行进行的所有数据更改
当您滚动光标时其结果集。数据值,
行的顺序和成员资格在每次获取时都可以更改。

并非所有查询都支持动态游标。问题中的代码可以,但如果没有 ORDER BY ,则无法确定行的处理顺序以及您是否会看到可见的结果。我添加了一个 ORDER BY 和一个索引来支持这一点,以允许使用动态游标。

如果您尝试以下操作,您将看到游标仅提取一行,因为日期按降序处理,并且当处理第一行时,表会更新,以便没有更多行符合下一次提取的条件。如果您注释掉游标循环内的UPDATE,则会获取所有三行。

CREATE TABLE MyTable
  (
     [Product]  INT,
     [Customer] INT,
     [Date]     DATETIME,
     [Event]    VARCHAR(10) NULL,
     PRIMARY KEY ([Date], [Product], [Customer])
  )


INSERT INTO MyTable 
VALUES (1,1,'20081201',NULL), 
       (1,1,'20081202',NULL), 
       (1,1,'20081203',NULL)

DECLARE @Product  INT,
        @Customer INT,
        @Date     DATETIME,
        @Event    VARCHAR(10)

DECLARE cur CURSOR DYNAMIC TYPE_WARNING FOR
  SELECT [Product],
         [Customer],
         [Date],
         [Event]
  FROM   MyTable
  WHERE  [Event] IS NULL
  ORDER  BY [Date] DESC

OPEN cur

FETCH NEXT FROM cur INTO @Product, @Customer, @Date, @Event

WHILE @@FETCH_STATUS = 0
  BEGIN
      SELECT @Product,
             @Customer,
             @Date,
             @Event

      -- Now I update my Event value to 'No Event' for records whose date is less than @Date
      UPDATE MyTable
      SET    [Event] = 'No Event'
      WHERE  [Product] = @Product
             AND [Customer] = @Customer
             AND [Date] < @Date

      FETCH NEXT FROM cur INTO @Product, @Customer, @Date, @Event
  END

CLOSE cur

DEALLOCATE cur

DROP TABLE MyTable 

Firstly You shouldn't need a cursor here. Something like the following would have the same semantics (from a starting position where all Event are NULL) and be more efficient.

WITH T
     AS (SELECT [Event],
                RANK() OVER (PARTITION BY [Product], [Customer] 
                                 ORDER BY [Date] DESC) AS Rnk
         FROM   MyTable)
UPDATE T
SET    [Event] = 'No Event'
WHERE  Rnk > 1 

Secondly regarding the question in the title to commit inside a cursor loop is the same as anywhere else. You just need a COMMIT statement. However if you aren't running this inside a larger transaction the UPDATE statement will be auto committed anyway.

Thirdly Your real question doesn't seem to be about commit anyway. It is about the cursor reflecting updates to the data on subsequent iterations. For the case in the question you would need a DYNAMIC cursor

Defines a cursor that reflects all data changes made to the rows in
its result set as you scroll around the cursor. The data values,
order, and membership of the rows can change on each fetch.

Not all queries support dynamic cursors. The code in the question would but without an ORDER BY it is undeterministic what order the rows would be processed in and thus whether you would see visible results. I have added an ORDER BY and an index to support this to allow a dynamic cursor to be used.

If you try the following you will see the cursor only fetches one row as the dates are processed in descending order and when the first row is processed the table is updated such that no more rows qualify for the next fetch. If you comment out the UPDATE inside the cursor loop all three rows are fetched.

CREATE TABLE MyTable
  (
     [Product]  INT,
     [Customer] INT,
     [Date]     DATETIME,
     [Event]    VARCHAR(10) NULL,
     PRIMARY KEY ([Date], [Product], [Customer])
  )


INSERT INTO MyTable 
VALUES (1,1,'20081201',NULL), 
       (1,1,'20081202',NULL), 
       (1,1,'20081203',NULL)

DECLARE @Product  INT,
        @Customer INT,
        @Date     DATETIME,
        @Event    VARCHAR(10)

DECLARE cur CURSOR DYNAMIC TYPE_WARNING FOR
  SELECT [Product],
         [Customer],
         [Date],
         [Event]
  FROM   MyTable
  WHERE  [Event] IS NULL
  ORDER  BY [Date] DESC

OPEN cur

FETCH NEXT FROM cur INTO @Product, @Customer, @Date, @Event

WHILE @@FETCH_STATUS = 0
  BEGIN
      SELECT @Product,
             @Customer,
             @Date,
             @Event

      -- Now I update my Event value to 'No Event' for records whose date is less than @Date
      UPDATE MyTable
      SET    [Event] = 'No Event'
      WHERE  [Product] = @Product
             AND [Customer] = @Customer
             AND [Date] < @Date

      FETCH NEXT FROM cur INTO @Product, @Customer, @Date, @Event
  END

CLOSE cur

DEALLOCATE cur

DROP TABLE MyTable 
新人笑 2024-09-09 15:13:35

即使这有效,也不能保证正确的结果,因为您在查询中错过了 ORDER BY 子句。

据此,可以更新所有记录、没有记录或记录的任何随机子集。

您能用简单的英语解释一下您的存储过程应该做什么吗?

Even if this worked, this would not guarantee the correct result since you miss an ORDER BY clause in your query.

Depending on this, all records, no records or any random subset of records could be updated.

Could you please explain in plain English what your stored procedure should do?

梦明 2024-09-09 15:13:35

使用下面的模板

DECLARE @CCount int = 100
    DECLARE @Count int = 0
    DECLARE  @id AS BigInt
    
        
    DECLARE cur Cursor fast_forward for 
        SELECT t1.Id  
             FROM Table1 t1 WITH (NOLOCK) WHERE  <Some where clause>
    OPEN cur
    Begin Tran
    
    While (1=1)
    Begin   
        Fetch next from cur into @id
        If @@Fetch_Status <> 0
            break
    -- do some DML actions
        Delete From Table1 WITH (ROWLOCK) where Id = @id
        Set @count = @count + @@Rowcount
    
        if (@count % @CCount = 0)
        Begin   
            if (@count % 100 = 0)
                Print 'Table1: DML action ' + Cast(@count as Varchar(15)) + ' rows'
    -- for every 100 rows commit tran will trigger , and starts a new one.
            While @@Trancount > 0 Commit Tran
            Begin Tran
        End
    End 
    
    While @@Trancount > 0 Commit Tran
    Close cur
    Deallocate cur

Use Below template

DECLARE @CCount int = 100
    DECLARE @Count int = 0
    DECLARE  @id AS BigInt
    
        
    DECLARE cur Cursor fast_forward for 
        SELECT t1.Id  
             FROM Table1 t1 WITH (NOLOCK) WHERE  <Some where clause>
    OPEN cur
    Begin Tran
    
    While (1=1)
    Begin   
        Fetch next from cur into @id
        If @@Fetch_Status <> 0
            break
    -- do some DML actions
        Delete From Table1 WITH (ROWLOCK) where Id = @id
        Set @count = @count + @@Rowcount
    
        if (@count % @CCount = 0)
        Begin   
            if (@count % 100 = 0)
                Print 'Table1: DML action ' + Cast(@count as Varchar(15)) + ' rows'
    -- for every 100 rows commit tran will trigger , and starts a new one.
            While @@Trancount > 0 Commit Tran
            Begin Tran
        End
    End 
    
    While @@Trancount > 0 Commit Tran
    Close cur
    Deallocate cur
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文