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
结果中的一条记录是否以不同方式处理来切换。对于我的具体情况,布尔标志就足够了......也许boolean
和case
的组合会起作用......
【参考方案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 - 你可以通过索引访问游标中的某些记录吗?的主要内容,如果未能解决你的问题,请参考以下文章