ORA-01002: 提取过程中的顺序不正确
Posted
技术标签:
【中文标题】ORA-01002: 提取过程中的顺序不正确【英文标题】:ORA-01002: fetch out of sequence in procedure 【发布时间】:2021-06-04 18:58:50 【问题描述】:我在 oracle 数据库中创建了一个过程,该过程在引用游标中返回数据,并且我希望它也将这个游标的行数作为输出变量返回。经测试,P_count 变量填写正确,但是当我试图打开它时
ORA-01002: fetch out of sequence
错误被触发。我以前读过它,我发现问题是因为我使用了 fetch 语句。但直到现在我还没有发现如何解决它。任何帮助表示赞赏,谢谢。 以下是我的程序:
PROCEDURE IS_CLIENT_LOGGED_IN (P_CLIENT_NUM Varchar2,P_CURSOR out SYS_REFCURSOR ,P_COUNT OUT NUMBER,P_ERROR out Varchar2) AS
TYPE MyRec IS RECORD (ID VARCHAR2(100));
cur_rec MyRec;
lv_cur SYS_REFCURSOR;
BEGIN
BEGIN
Open lv_cur FOR
SELECT ID
FROM tbl_registration
WHERE tbl_client_id = P_CLIENT_NUM
AND tbl_logout_date is null;
LOOP
FETCH lv_cur INTO cur_rec;
EXIT WHEN P_CURSOR%notfound;
P_COUNT := P_CURSOR%rowcount;--will return row number beginning with 1
END LOOP;
P_CURSOR := lv_cur;
EXCEPTION WHEN OTHERS THEN
P_ERROR := 'Unable to select Data from tbl_registration' ||SQLERRM;
END;
网上搜索发现问题原因如下:
-
在检索到最后一行并返回 ORA-1403 错误后从游标中提取。
如果游标已使用 FOR UPDATE 子句打开,则在发出 COMMIT 后进行提取将返回错误。
重新绑定 SQL 语句中的所有占位符,然后在重新执行语句之前发出 fetch。
但我找不到合适的解决方案。
【问题讨论】:
很难预先获得要运行的查询的行数。本质上,这意味着运行相同的查询两次,或者运行查询,将结果读入数组并基于数组运行第二个查询。所以问问自己:真的有必要获取行数吗?我从来没有遇到过那种特殊的情况。 【参考方案1】:你想要的听起来很简单,但事实并非如此。游标仅供一次性使用。所以如果你通读它,你就完成了。因此,返回未使用的游标及其行数需要特殊处理。
想到的一种方法是两个单独的查询:一个用于计算行数,一个用于打开游标。但问题是:在查询之间的纳秒内,行数可能会因为其他会话插入、删除或更新数据而发生变化。好的,您可以独占锁定表,但是您必须在两个语句之后释放它。不幸的是,Oracle 中没有UNLOCK TABLE
命令。只能通过COMMIT
或ROLLBACK
释放表。因此,为了不干扰调用者的事务,您还需要一个SAFEPOINT
。
这是您应用上述方法的过程:
CREATE OR REPLACE PROCEDURE is_client_logged_in (p_client_num VARCHAR2, p_cursor OUT SYS_REFCURSOR, p_count OUT NUMBER, p_error OUT VARCHAR2) AS
BEGIN
SAVEPOINT for_unlock;
LOCK TABLE tbl_registration IN EXCLUSIVE MODE;
SELECT COUNT(*) INTO p_count
FROM tbl_registration
WHERE tbl_client_id = p_client_num
AND tbl_logout_date IS NULL;
Open p_cursor FOR
SELECT id
FROM tbl_registration
WHERE tbl_client_id = p_client_num
AND tbl_logout_date IS NULL;
ROLLBACK TO SAVEPOINT for_unlock;
EXCEPTION WHEN OTHERS THEN
ROLLBACK TO SAVEPOINT for_unlock;
p_error := 'Unable to select Data from tbl_registration' || SQLERRM;
END is_client_logged_in;
演示:https://dbfiddle.uk/?rdbms=oracle_18&fiddle=552d7f76d03a9a221c17f05c6663d2c9
【讨论】:
【参考方案2】:您只能使用一次游标,因此如果您需要行数和实际结果集,则需要两个单独的查询。那么问题是数据可能已被两个查询之间的其他会话更新,从而产生不一致的结果。 (例如,另一个会话之前插入了一些行,然后在您的过程运行时提交。)
一种方法是使用flashback query 来确保计数和提取指的是同一时间点:
create or replace procedure is_client_logged_in
( p_client_num varchar2
, p_cursor out sys_refcursor
, p_count out number
, p_error out varchar2 )
as
k_starttime constant timestamp := systimestamp;
begin
select count(*) into p_count
from tbl_registration as of timestamp k_starttime
where tbl_client_id = p_client_num
and tbl_logout_date is null;
open p_cursor for
select id
from tbl_registration as of timestamp k_starttime
where tbl_client_id = p_client_num
and tbl_logout_date is null;
exception
when others then p_error := 'Unable to retrieve registration data.'||chr(10)||sqlerrm;
end;
【讨论】:
我非常喜欢这种方法。我喜欢这个例子,它清楚地说明了在运行程序的那一刻数据可以多么容易地改变。【参考方案3】:这是另一种选择。真的是糊里糊涂,不过好吧……
我已经展示了锁定表格的解决方案。但是想象一个运行一个小时的查询。这次你会锁定那个表两次。
William 展示了一个很好的闪回查询解决方案。但是查询运行一个小时,闪回数据可能会超时。
所以,这是一个奇怪的解决方案,获取计数并在一个查询中产生结果。在你的情况下这很容易,因为我希望你选择的 ID 是一个整数,就像计数一样。因此,我将计数选择到第一行,获取该计数并返回当前位于第一个实际数据行(第一个 ID)位置的游标。
CREATE OR REPLACE PROCEDURE is_client_logged_in (p_client_num VARCHAR2, p_cursor OUT SYS_REFCURSOR, p_count OUT NUMBER, p_error OUT VARCHAR2) AS
BEGIN
Open p_cursor FOR
WITH data AS
(
SELECT id
FROM tbl_registration
WHERE tbl_client_id = p_client_num
AND tbl_logout_date IS NULL
)
SELECT id
FROM
(
SELECT 1 as sortkey, COUNT(*) AS id FROM data
UNION ALL
SELECT 1 as sortkey, id FROM data
)
ORDER BY sortkey;
FETCH p_cursor INTO p_count;
EXCEPTION WHEN OTHERS THEN
p_error := 'Unable to select Data from tbl_registration' || SQLERRM;
END is_client_logged_in;
演示:https://dbfiddle.uk/?rdbms=oracle_18&fiddle=24ed333b9c1ed700af6d3a486ef47ccd
【讨论】:
以上是关于ORA-01002: 提取过程中的顺序不正确的主要内容,如果未能解决你的问题,请参考以下文章