【问题标题】:ORA-01002: fetch out of sequence in procedureORA-01002: 提取过程中的顺序不正确
【发布时间】: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; 

网上搜索发现问题原因如下:

  1. 在检索到最后一行并返回 ORA-1403 错误后从游标中提取。
  2. 如果游标已使用 FOR UPDATE 子句打开,则在发出 COMMIT 后进行提取将返回错误。
  3. 重新绑定 SQL 语句中的所有占位符,然后在重新执行语句之前发出 fetch。

但我找不到合适的解决方案。

【问题讨论】:

  • 很难预先获得要运行的查询的行数。本质上,这意味着运行相同的查询两次,或者运行查询,将结果读入数组并基于数组运行第二个查询。所以问问自己:真的有必要获取行数吗?我从来没有遇到过那种特殊的情况。

标签: oracle plsql cursor


【解决方案1】:

你想要的听起来很简单,但事实并非如此。游标仅供一次性使用。所以如果你通读它,你就完成了。因此,返回未使用的游标及其行数需要特殊处理。

想到的一种方法是两个单独的查询:一个用于计算行数,一个用于打开游标。但问题是:在查询之间的纳秒内,行数可能会因为其他会话插入、删除或更新数据而发生变化。好的,您可以独占锁定表,但是您必须在两个语句之后释放它。不幸的是,Oracle 中没有UNLOCK TABLE 命令。只能通过COMMITROLLBACK 释放表。因此,为了不干扰调用者的事务,您还需要一个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

    【讨论】:

      猜你喜欢
      • 2013-04-19
      • 2016-07-11
      • 1970-01-01
      • 2023-03-04
      • 2020-10-13
      • 2016-06-09
      • 1970-01-01
      相关资源
      最近更新 更多