游标循环SQL Server

时间:2016-03-05 14:46:18

标签: sql-server cursor

我需要一些SQL Server游标的帮助:

     DECLARE @iTerminalID varchar(100)
     DECLARE @iID int
     DECLARE @iDateTB date
     DECLARE @iNom bigint
     DECLARE @iTBtoEOD bigint
     DECLARE @iSal bigint
     DECLARE @iOldNom bigint
     DECLARE @iOldSal bigint
     DECLARE @check int

     DECLARE main_cursor CURSOR FOR
     SELECT
       TerminalId
     FROM AA
     WHERE Nom IS NULL
     GROUP BY TerminalId
     ORDER BY TerminalId

     OPEN main_cursor
     FETCH NEXT
     FROM main_cursor INTO @iTerminalID
     WHILE (@@fetch_status = 0)

     BEGIN

       SET @iOldSal = 0

       DECLARE detail_cursor CURSOR FOR
       SELECT
         ROW_NUMBER() OVER (ORDER BY TerminalId, DateTB) NoID,
         DateTB,
         Nomi,
         TBtoEOD,
         Sal
       FROM AA
       WHERE TerminalId = @iTerminalID

       OPEN detail_cursor
       FETCH NEXT
       FROM detail_cursor INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
       WHILE (@@fetch_status = 0)
       BEGIN
         IF  @iNom IS NULL AND @iID = 1        
         BEGIN
           UPDATE AA
           SET Nom = 0
           WHERE TerminalId = @iTerminalID
           AND CONVERT(date, TanggalTB) = CONVERT(date, @iDateTB)        
           SET @iOldSal = abs(@iTBtoEOD)
         END
         ELSE IF @iNom IS NULL
           AND @iID <> 1
           --AND @iOldSal <> 0
         BEGIN
           UPDATE AA
           SET Nom = @iOldSal
           WHERE TerminalId = @iTerminalID
           AND CONVERT(date, DateTB) = CONVERT(date, @iDateTB)
           PRINT concat(@iTerminalID, '----', @iDateTB)
           SET @iOldSal = @iNom - @iTBtoEOD
         END
         ELSE
         BEGIN
           SET @iOldSal = @iNom - @iTBtoEOD
         END

         FETCH detail_cursor INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
       END
       CLOSE detail_cursor
       DEALLOCATE detail_cursor

       FETCH main_cursor INTO @iTerminalID
     END
     CLOSE main_cursor
     DEALLOCATE main_cursor

我有2个游标 用于循环到详细数据的第一个游标 在我得到我所拥有的数据的详细信息之后,我将根据我编写的计算执行更新

直到光标到2

完成更新过程后才能解决此问题

2 个答案:

答案 0 :(得分:1)

正如marc_s所建议的那样,除非你别无选择,否则最好除掉SQL Server开发解决方案中的SQL cursor

我准备了以下T-SQL Update命令,其中没有使用游标

UPDATE AA
set 
    Nomi = case when ( (NewAA.NoID = 1) and (AA.Nomi is null) ) 
            then 0 
            when ( (NewAA.NoID > 1) and (AA.Nomi is null) )
            then (select sum(t.Nomi) from AA t where t.DateTB < AA.DateTB and t.TerminalId = AA.TerminalId)
            else AA.Nomi
        end 
FROM AA
Inner Join (
    SELECT
        PK_Field,
        NoID = ROW_NUMBER() OVER (Partition By TerminalId ORDER BY DateTB),
        DateTB,
        Nomi,
        TBtoEOD,
        Sal
    FROM AA
) NewAA 
    on AA.PK_Field = NewAA.PK_Field

请注意,我使用了名为PK_Field的主键字段。 我希望你的桌子上有主键AA

我不确定Update语句的详细信息,请先在测试环境中测试上面的sql代码

答案 1 :(得分:0)

审查每一步的差异

第1步

  
      
  • 指定光标的类型/方向
  •   
  • 不是group_by而是分明,不要迷惑自己
  •   
  • in row_number在通过单个@iTerminalId
  • 过滤时由TerminalId进行不必要的排序   
  • 为抓取和其他陈述保存相同的语法
  •   
  • 看起来你需要按升序排列row_numbered - 你必须明确指定order_by
  •   
DECLARE
    @main_cursor    cursor,
    @detail_cursor  cursor

 SET @main_cursor = CURSOR FAST_FORWARD FOR
 SELECT DISTINCT
   TerminalId
 FROM AA
 WHERE Nom IS NULL
 ORDER BY TerminalId

 OPEN @main_cursor

 FETCH NEXT FROM @main_cursor
 INTO @iTerminalID

 WHILE (@@fetch_status = 0)
 BEGIN
   SET @iOldSal = 0

   SET @detail_cursor = CURSOR FAST_FORWARD FOR
   SELECT
     ROW_NUMBER() OVER (ORDER BY DateTB) as NoID,
     DateTB, Nomi, TBtoEOD, Sal
   FROM AA
   WHERE TerminalId = @iTerminalID
   ORDER BY NoID

   OPEN @detail_cursor

   FETCH NEXT FROM @detail_cursor
   INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal

   WHILE (@@fetch_status = 0)
   BEGIN
     ... same inner part for now

     FETCH NEXT FROM @detail_cursor
     INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
   END
   CLOSE @detail_cursor
   DEALLOCATE @detail_cursor

   FETCH NEXT FROM @main_cursor
   INTO @iTerminalID
 END
 CLOSE @main_cursor
 DEALLOCATE @main_cursor

第2步

  
      
  • 外部循环除了归零@iOldSal
  • 之外什么都不做   
  • 另一件事 - row_number以1为每个TerminalID
  • 开头   
  • 所以正确顺序所需的所有数据都可以通过单一查询获得
  •   
SET @detail_cursor = CURSOR FAST_FORWARD FOR
SELECT
  a.TerminalId,
  ROW_NUMBER() OVER (PARTITION BY a.TerminalId ORDER BY a.DateTB) as NoID,
  a.DateTB, a.Nomi, a.TBtoEOD, a.Sal
FROM AA a
ORDER BY a.TerminalId, NoID

OPEN @detail_cursor

FETCH NEXT FROM @detail_cursor
INTO @TerminalId, @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal

WHILE (@@fetch_status = 0)
BEGIN
  if @iID = 1
    SET @iOldSal = 0

  ... same inner part for now

  FETCH NEXT FROM @detail_cursor
  INTO @TerminalId, @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
END
CLOSE @detail_cursor
DEALLOCATE @detail_cursor

第3步

以下更改我假设您在DateTB顺序中为每个终端重新编号行 并且您在第一次更新时有拼写错误(TanggalTB列而不是DateTB) 并且TBtoEOD是某种始终为负的偏移量 并且每次更新只影响一行。这些假设可能不正确 - 请在评论中随意注明。

  
      
  • @iOldSal计算总是相同的,除了@ iNum = NULL,可以借助IsNull()来处理
  •   
  • 当@ iID = 1时@iOldSal始终为0所以首先IF没有意义 - 它与第二个相同
  •   
  • 当@iNom为NULL时@iNom - @iTBtoEOD将返回NULL - 您必须初始化它
  •   
  • 光标处理意味着您确实有一个光标位于当前处理的行上,因此您不必在更新中再次找到它   声明;但是你需要修改游标类型(FAST_FORWARD包含   READONLY)FORWARD_ONLY(可能是FOR_UPDATE - 只是可以&#39; t   记得;可能是sqlserver不允许它是&#34; FOR_UPDATE&#34;   因为窗口功能,我没有检查)
  •   
  • 许多变量变得不必要,因为我们直接引用行值而不需要重复搜索当前处理的行
  •   
SET @detail_cursor = CURSOR FORWARD_ONLY, FOR_UPDATE FOR
SELECT
  ROW_NUMBER() OVER (PARTITION BY a.TerminalId ORDER BY a.DateTB) as NoID,
  a.Nom
FROM AA a
ORDER BY a.TerminalId, NoID

FETCH NEXT FROM @detail_cursor
INTO @iID, @iNom

WHILE (@@fetch_status = 0)
BEGIN
  IF @iID = 1
    SET @iOldSal = 0

  UPDATE a SET
    Nom = IsNull(a.Nom, @iOldSal),
    @iOldSal = a.Nom - a.TBtoEOD
  FROM AA a
  WHERE CURRENT OF @detail_cursor

  FETCH NEXT FROM @detail_cursor
  INTO @iID, @iNom
END
CLOSE @detail_cursor
DEALLOCATE @detail_cursor

第4步

所以你只是根据先前编号的行设置一个不具备它的行? 不确定我是否理解一切正确,但在我看来,这个任务可以用这样的代码完成:

;WITH cteAA as
(
  SELECT
    a.TerminalID, a.TBtoEOD, a.Nom,
    ROW_NUMBER() OVER (PARTITION BY a.TerminalId ORDER BY a.DateTB, a.Nom) as NoID
  FROM AA a
),
cteAANums as
(
  SELECT a.TerminalID, a.NoID, IsNull(a.Nom, 0) as Nom, a.TBtoEOD
  FROM cteAA a
  WHERE a.NoID = 1

  UNION ALL

  SELECT a.TerminalID, a.NoID, IsNull(a.Nom, n.Nom-n.TBtoEOD) as Nom, a.TBtoEOD
  FROM cteAA a
  INNER JOIN cteAANums n on n.TerminalID = a.TerminalID
  WHERE a.NoID = n.NoID + 1
)
UPDATE a SET
  Nom = n.Nom
FROM AA a
INNER JOIN cteAANums n on n.TerminalID = a.TerminalID and a.DateTB = n.DateTB

没有自连接就无法实现,对于其他情况,光标可能不是一个糟糕的选择。但不是唯一肯定的。