PL/SQL - 你能通过索引访问游标中的特定记录吗?

PL/SQL - Can you access certain record in a cursor by index?

我正在尝试编写测试,并给出了一组评论,我想将其中 1 设置为 'Status A' 和 all其余 至 'Status 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;

您可以使用 BOOLEAN 来告诉您是否已处理游标中的 "first" 行,其方式类似于以下内容:

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;

祝你好运。

您可以使用 "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;   

无论如何你不应该滥用这个:除非你需要多次访问数据(所以将它保存在内存中而不是重新执行查询可以加快速度)你应该尝试使用常规游标。

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

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

与游标

一起使用索引table
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;

使用索引 table 代替游标

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;