PL/SQL - 你可以通过索引访问游标中的某些记录吗?

Posted

技术标签:

【中文标题】PL/SQL - 你可以通过索引访问游标中的某些记录吗?【英文标题】:PL/SQL - Can you access certain record in a cursor by index? 【发布时间】:2017-10-10 11:48:15 【问题描述】:

我正在尝试编写一个测试,并且给定一组评论,我想将其中 1 个设置为“状态 A”,其余所有 设置为'状态 B'。我知道对于 PL/SQL,有一个用于 CURSOR 的 FOR LOOP 语法,我得到了它,所以它可以处理所有评论,但是有没有办法访问该游标中的特定记录?

我认为可以解决我的问题的是一种按索引访问光标的方法,类似于在其他语言中按索引访问数组的方式。 有没有办法在 PL/SQL 中做到这一点?

我对 PL/SQL 和游标的语法还是很陌生,所以我会用伪代码写出我想做的事情。

伪代码

CURSOR c_reviewer_ids IS
    SELECT id FROM reviewer_tbl
    WHERE course_id = 123;
v_first_id   reviewer_tbl.id%TYPE;

FOR i in 0..c_reviewer_ids.length

   IF i == 0
   
      v_first_id := c_reviewer_ids(i);

      UPDATE reviewer_tbl
      SET status = 'Status A'
      WHERE id = c_reviewer_ids(i);
   
   ELSE
   
      UPDATE reviewer_tbl
      SET status = 'Status B'
      WHERE id = c_reviewer_ids(i);
   

我能够制作一个 CURSOR FOR LOOP,但它处理每条记录的方式相同,我只想为一条记录做一些特别的事情。这是我目前拥有的:

CURSOR FOR LOOP(不处理特殊情况)

  CURSOR c_reviewer_ids IS
    SELECT id FROM reviewer_tbl
    WHERE course_id = 123;
  v_reviewer_id   reviewer_tbl.id%TYPE;

  FOR l_reviewer_id IN c_reviewer_ids 
  LOOP
      --Set the status for all reviewers.
      UPDATE reviewer_tbl
      SET status = 'Status B'
      WHERE reviewer_id = l_reviewer_id.id;

      --Save one of the ids; for this particular test, it doesn't matter if it is first or not
      v_reviewer_id := l_reviewer_id.id;
  END LOOP;

【问题讨论】:

你为什么需要一个光标? case 构造不能解决问题吗? @dan-bracuk,我不知道测试前的审稿人 ID,所以我无法将它们用作在case 语句中打开的条件。我只关心根据select 结果中的一条记录是否以不同方式处理来切换。对于我的具体情况,布尔标志就足够了......也许booleancase的组合会起作用...... 【参考方案1】:

您可以使用 BOOLEAN 来告诉您是否已经处理了光标的“第一”行,方式类似于以下:

DECLARE
  CURSOR c_reviewer_ids IS
    SELECT id FROM reviewer_tbl
      WHERE course_id = 123;
  v_reviewer_id   reviewer_tbl.id%TYPE;
  bFirst_row      BOOLEAN := TRUE;
BEGIN
  FOR l_reviewer_id IN c_reviewer_ids 
  LOOP
    IF bFirst_row THEN
      bFirst_row := FALSE;

      UPDATE reviewer_tbl
        SET status = 'Status A'
        WHERE id = l_reviewer_id.ID;
    ELSE
      --Set the status for all other reviewers.
      UPDATE reviewer_tbl
        SET status = 'Status B'
        WHERE reviewer_id = l_reviewer_id.ID;
    END IF;

    --Save one of the ids; for this particular test, it doesn't matter if it is first or not
    v_reviewer_id := l_reviewer_id.id;
  END LOOP;
END;

祝你好运。

编辑

如果您想访问第五行,您可以执行以下操作:

DECLARE
  v_reviewer_id   reviewer_tbl.id%TYPE;
BEGIN
  FOR l_reviewer_id IN (SELECT id, RN
                          FROM (SELECT ID, ROWNUM AS RN
                                  FROM reviewer_tbl
                                  WHERE course_id = 123)
                          WHERE RN = 5)
  LOOP
      UPDATE reviewer_tbl
        SET status = 'Status A'
        WHERE id = l_reviewer_id.ID;

    --Save one of the ids; for this particular test, it doesn't matter if it is first or not
    v_reviewer_id := l_reviewer_id.id;
  END LOOP;
END;

祝你好运。

【讨论】:

谢谢 - 这能够解决我的具体问题。 :) 我仍然很好奇是否有更通用的解决方案......例如,如果我需要访问第 5 行怎么办?有没有办法访问索引5的记录? 在提取五行之前没有第 5 行。完整的结果集在某些内部存储中不存在,等待您访问。如果有帮助,您可以使用 row_number() 分析函数将计算的行号添加到游标。 查看我对答案的编辑。另请注意,在没有ORDER BY 子句的情况下,“第五行”的概念是任意的。祝你好运。【参考方案2】:

您可以使用“BULK COLLECT INTO”子句将所有查询结果加载到实际内存数组中,然后使用基于索引的常规访问该数组。

这不是内存效率的(因此请注意,如果您正在处理大量记录,则不应这样做),但它确实有效:

看这个例子,我在内存数组中加载了“DICT”系统视图的前 100 条记录:

 declare
    type MEMTABLE_TYPE is 
        TABLE OF DICT%ROWTYPE index by binary_integer;

    myarray MEMTABLE_TYPE;
 begin
   select * 
   BULK COLLECT INTO myarray -- this loads the whole query result into the array
   from DICT
   where rownum < 100;

   -- scan all the array: 
   for c in 1..myarray.count loop
      dbms_output.put_line(myarray(c).table_name ||' -> ' || myarray(c).comments);
   end loop;             

   -- access directly the fifth element  
   dbms_output.put_line(myarray(5).table_name ||' -> ' || myarray(5).comments);
 end;   

无论如何,您不应该滥用这一点:除非您需要多次访问数据(因此将其保存在内存中而不是重新执行查询可以加快处理速度),否则您应该尝试使用常规游标。

【讨论】:

【参考方案3】:

使用this from the Oracle documentation,我找到了一种使用INDEX-BY 表的方法,该表与我在伪代码中想要的最接近。它创建游标,然后将其放入临时表中,然后可以对其进行索引。

写完代码后,我意识到游标可能不是那么必要,因为它是立即关闭的。在这种情况下,可以省略光标,只使用 INDEX BY 表(类似于 Carlo 在他的回答中写的)。

使用索引表 ALONG WITH 游标

DEFINE
    CURSOR c_reviewer_ids IS
        SELECT id FROM reviewer_tbl
        WHERE course_id = 123;

    --table type
    TYPE reviewer_id_tbl_type IS TABLE OF c_reviewer_ids%ROWTYPE INDEX BY PLS_INTEGER;
    --actual table
    t_reviewer_ids reviewer_id_tbl_type;

    v_first_id   reviewer_tbl.id%TYPE;
BEGIN
    --Fetch the cursor into the indexed table
    OPEN c_reviewer_ids;
    FETCH c_reviewer_ids BULK COLLECT INTO t_reviewer_ids;
    CLOSE c_reviewer_ids;  --cursor won't be used anymore

    --For loop
    FOR i IN 1..t_reviewer_ids.COUNT()  LOOP
       IF i = 1 THEN
          v_first_id := t_reviewer_ids(i).id;

          UPDATE reviewer_tbl
          SET status = 'Status A'
          WHERE id = t_reviewer_ids(i).id;
       ELSE
          UPDATE reviewer_tbl
          SET status = 'Status B'
          WHERE id = t_reviewer_ids(i).id;
       END IF;
    END LOOP;
END;

使用索引表代替游标

DEFINE
    --table type
    TYPE reviewer_id_tbl_type IS TABLE OF reviewer_tbl.id%TYPE INDEX BY PLS_INTEGER;
    --actual table
    t_reviewer_ids reviewer_id_tbl_type;

    v_first_id   reviewer_tbl.id%TYPE;
BEGIN
    --Fetch the query into the indexed table
    SELECT id
    BULK COLLECT INTO t_reviewer_ids
    FROM reviewer_tbl
    WHERE course_id = 123;

    --For loop; Notice that .id is not needed anymore
    FOR i IN 1..t_reviewer_ids.COUNT()  LOOP
       IF i = 1 THEN
          v_first_id := t_reviewer_ids(i); 

          UPDATE reviewer_tbl
          SET status = 'Status A'
          WHERE id = t_reviewer_ids(i);
       ELSE
          UPDATE reviewer_tbl
          SET status = 'Status B'
          WHERE id = t_reviewer_ids(i);
       END IF;
    END LOOP;
END;

【讨论】:

以上是关于PL/SQL - 你可以通过索引访问游标中的某些记录吗?的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle PL/SQL 中获取具有索引而不是列名的字段

PL/SQL 异常 ORA-06511 游标已打开

PL/SQL中的游标

过程中的PL/SQL游标问题

PL/SQL:游标使用中的 ORA-01001

过程(PL/SQL 包)中的“ORA-01001 无效游标”