【问题标题】:How to handle NO_DATA_FOUND in sys-refcursor using bind variables如何使用绑定变量处理 sys-refcursor 中的 NO_DATA_FOUND
【发布时间】:2019-10-08 06:29:37
【问题描述】:

select语句在SYS_REFCURSOR中没有返回任何记录的情况如何处理? 我正在使用绑定变量的动态 sql 生成过程。

 create or replace
        procedure test_dynamic_sql
          (
            last_name varchar2,
            rc out sys_refcursor,
            jobid varchar2,
            sdate date,
            edate date,
            status out varchar2,
            message out varchar2 )
        is
          q long;
          lname varchar2(240);

        begin
           q := 'select employee_id    
        from employees e           
        where 1=1';
            if last_name is not null then
              q := q || 'and (e.LAST_NAME = :LAST_NAME)';
            else
              q := q || 'and (1=1 or :LAST_NAME is null)';
            end if;
            if jobid is not null then
              q := q || 'and (e.JOB_ID = :JOBID)';
            else
              q := q || 'and (1=1 or :JOBID is null)';
            end if;
            if sdate is not null then
              q := q || 'and (e.hire_date >= :sdate)';
            else
              q := q || 'and (1=1 or :sdate is null)';
            end if;
            if edate is not null then
              q := q || 'and (e.hire_date <= :edate)';
            else
              q := q || 'and (1=1 or :edate is null)';
            end if;

            open rc for q using last_name, jobid, sdate, edate;
          /*     
          IF rc%NOTFOUND THEN
            STATUS  := 'NR';
            MESSAGE := 'Not Found';
          ELSE
            STATUS  := 'S';
            MESSAGE := 'Found';
          END IF;
          */ 

        exception
        when others then
          STATUS  :='E';
          message := sqlcode||'-->'||sqlerrm;
        end;

我尝试了%NOTFOUND%FOUND 属性,但它不起作用。 我也尝试过NO_DATA_FOUND 异常,但它也不起作用。

我需要将状态返回为“S”、“E”、“NR”

  • S --> 成功(找到记录时)
  • E --> ERROR(发生任何错误时)
  • NR--> 无记录(当 0 条记录时)

谢谢!

【问题讨论】:

  • 感谢 SKG。您能否确认这里的目标是报告游标是否有任何数据,但您不能/不打算从中 FETCH 或使用它的数据?
  • 您需要在 where 子句中添加的每个条件之间添加一个空格以避免错误。为了检查是否存在,我会将查询更改为返回 SELECT COUNT(1),因此它将返回 0 表示未找到任何行,并添加一个 and rownum &lt; 2 以限制为获得结果而处理的行。或者,保留所有内容,只需执行一次 FETCH 并检查 %ROWCOUNT
  • 目前没有什么可以阻止代码找到两个同名员工。如果传递给此过程的唯一参数是名称,并且所有其他参数都作为 NULL 传递,则代码当前不处理此问题。
  • @alexgibbs 它正在为 out 参数提供输出,我可以使用匿名块打印记录。此代码中是否真的需要 fetch 语句,因为我在 rec 中获取输出,即 sys_refcursor out 参数。我会很感激你的帮助。
  • @CodeNovice 这只是一个示例代码。主要关注的是处理“未找到数据”。在我的原始代码中,将传递唯一的供应商代码。如果有同名的记录,那么问题是什么?它会根据搜索条件返回多条记录,如果我们需要区分,则必须传递其他参数,如加入日期、出生日期等。

标签: oracle plsql sys-refcursor


【解决方案1】:

如果您希望以尝试的方式使用游标,而不管由于动态 SQL 或显式游标而导致的隐式 Ref 游标,您会遗漏一些事情。

为了使用 %ROWCOUNT 或 %NOTFOUND,您必须首先获取光标。此链接"PL/SQL 101 : Understanding Ref Cursors" 提供了有关此主题的大量信息,但帮助回答您的问题所需要做的就是知道您必须先获取数据。

下图显示了我的员工表中的数据。 请注意,有两名员工的姓氏为“Loch”

下面的代码都在它自己的匿名块中,但可以很容易地转换为过程/函数。它具有您所需的所有状态和消息。为了处理具有多个结果的搜索,我添加了一个额外的状态/消息来告诉用户返回了多条记录。最后,为了更容易使用您的代码,我取出了除两个参数之外的所有参数。 注意:如果过程的参数全部作为 NULL 传入,则生成的动态 SQL 将查询整个表,因为它基本上删除了 WHERE 子句中的所有过滤器

DECLARE

  /* Parameters */
  rc              SYS_REFCURSOR;
  q               VARCHAR2(200);
  status          VARCHAR2(5);
  message         VARCHAR2(100);

  /* Declare Record used to FETCH data into */
  rec_employee    employees%ROWTYPE;  

  /* Bind Variables */
  jobid           NUMBER        :=  null;
  last_name       VARCHAR2(240) :=  'Loch';  

BEGIN

  /* Dynamic SQL statement with placeholder: */
  q := 'SELECT * FROM employees WHERE 1=1';

    IF last_name IS NOT NULL
      THEN q := q || ' AND (lastname = :LAST_NAME)';
      ELSE q := q || ' AND (1=1 or :LAST_NAME is null)';
    END IF;

    IF jobid IS NOT NULL
      THEN q   := q || ' AND (ID = :JOBID)';
      ELSE q   := q || ' AND (1=1 or :JOBID is null)';
    END IF;

  /* Open cursor & specify bind argument in USING clause: */
  OPEN rc FOR q USING last_name, jobid;    

  LOOP
    /* In order to work with any Data that a Cursor 'points' to we must FETCH the data.  This allows us to use %ROWCOUNT and %NOTFOUND */
    FETCH rc INTO rec_employee;    
    EXIT WHEN rc%NOTFOUND;    
  END LOOP;

  IF rc%ROWCOUNT = 0 THEN
    STATUS  :=  'NR';
    MESSAGE :=  'Not Found';
    --EXIT;
  ELSIF rc%ROWCOUNT = 1 THEN
    STATUS  :=  'S';
    MESSAGE :=  'Found';
    --EXIT;
  ELSE
    STATUS  :=  'MU';
    MESSAGE :=  'Multiple Records';

  END IF;

  dbms_output.put_line('Final Count: ' || rc%ROWCOUNT);

  /* Close Cursor */
  CLOSE rc;

  /* Return Variables or Provide Output to User */
  dbms_output.put_line('STATUS: ' || STATUS);
  dbms_output.put_line('MESSAGE: ' || MESSAGE);

EXCEPTION
        WHEN OTHERS THEN
          STATUS  :='E';
          message := sqlcode||'-->'||sqlerrm;
          dbms_output.put_line('STATUS: ' || STATUS);
          dbms_output.put_line('MESSAGE: ' || MESSAGE);

END;

【讨论】:

  • 感谢您的帮助。
  • @SKG 请务必...如果我的回答对您有帮助或满足您的要求,请接受此作为答案或简单地将我的回答标记为有帮助。我对这个问题很感兴趣,我花了很多时间才想出答案。通过这个,我学到了很多东西。
  • 当然....目前我正在根据您的建议更改我的代码。我已经编译了你的代码并且它正在工作。但是现在我正在根据我的要求进行一些更改。一旦完成,我会标记你的答案。
  • CodeNovice:嗨...非常抱歉!我仍然面临一些问题。当我将其转换为过程时,我将 sys refcursor 变量声明为 out 参数并获取记录,它给了我计数、状态和消息,但它不返回记录并给出错误“无效游标”。
  • 嗯,很有趣。你在程序之外的代码中用光标做什么?请记住,游标是指向查询的指针,而不是数据。如果您尝试在过程之外使用游标,请确保打开游标并再次使用 FETCH 告诉 Oracle 使用游标查询来获取数据。如果您不先打开游标,您将收到错误“无效游标”。在不了解更多信息或查看您的“其他”代码的情况下,我只能猜测出了什么问题。
【解决方案2】:

这个答案是为了解决您在使用 Ref 游标作为输出参数时遇到的问题。下面的代码调用您的test_dynamic_sql() 程序。在这个过程中,我们OPEN 光标,FETCH 它指向的数据,我们不CLOSE 光标,因为我们立即在test_dynamic_sql() 过程之外再次使用该光标。 注意的一件事 - 当使用 FETCH 时,Cursor 将不再为您提供数据,必须再次打开。由于您的游标使用的是动态 SQL,我们必须在声明其余全局变量的同一位置声明我们的动态“查询”。

“光标不是为重复使用而设计的:您只需阅读一次,然后继续前进,在这样做的同时,您将丢弃任何以前扫描过的行。”这个事实是从这个 SO 帖子中窃取的:Oracle. Reuse cursor as parameter in two procedures

在此过程之外,我们首先必须通过使用 IF 语句检查游标是否存在来检查游标是否成功初始化:IF (g_rc IS NOT NULL) THEN

下面的完整代码示例:

DECLARE

  /* g for Global */
  g_status          VARCHAR2(5);
  g_message         VARCHAR2(100);

  g_rc              SYS_REFCURSOR;

  /* Store Dynamic SQL Query */
  g_SQL             VARCHAR2(200);

  /* Bind Variables */
  g_jobid           NUMBER;
  g_last_name       VARCHAR2(240);

  /* Declare Global Record used to FETCH data into */
  g_rec_employee      employees%ROWTYPE;

  PROCEDURE test_dynamic_sql(pv_last_name VARCHAR2,
                              p_rc OUT SYS_REFCURSOR,
                              pv_jobid VARCHAR2,
                              pv_status OUT VARCHAR2,
                              pv_message OUT VARCHAR2,
                              pv_q OUT VARCHAR2)
  AS

    /* Declare Record used to FETCH data into */
    rec_employee    employees%ROWTYPE;  

    /* Bind Variables */
    jobid           NUMBER        :=  to_number(pv_jobid);
    last_name       VARCHAR2(240) :=  pv_last_name;  

  BEGIN

    /* Reset/Initialize Cursor Output Variable */
    p_rc            :=  NULL;

    /* Dynamic SQL statement with placeholder: */
    pv_q := 'SELECT * FROM employees WHERE 1=1';

      IF last_name IS NOT NULL
        THEN pv_q := pv_q || ' AND (lastname = :LAST_NAME)';
        ELSE pv_q := pv_q || ' AND (1=1 or :LAST_NAME is null)';
      END IF;

      IF jobid IS NOT NULL
        THEN pv_q   := pv_q || ' AND (ID = :JOBID)';
        ELSE pv_q   := pv_q || ' AND (1=1 or :JOBID is null)';
      END IF;

    /* Open cursor & specify bind argument in USING clause: */
    OPEN p_rc FOR pv_q USING last_name, jobid;    

    LOOP
      /* In order to work with any Data that a Cursor 'points' to we must FETCH the data.  This allows us to use %ROWCOUNT and %NOTFOUND */
      FETCH p_rc INTO rec_employee;     
      EXIT WHEN p_rc%NOTFOUND;    
    END LOOP;

    IF p_rc%ROWCOUNT = 0 THEN
      pv_status  :=  'NR';
      pv_message :=  'Not Found';
      --EXIT;
    ELSIF p_rc%ROWCOUNT = 1 THEN
      pv_status  :=  'S';
      pv_message :=  'Found';
      --EXIT;
    ELSE
      pv_status  :=  'MU';
      pv_message :=  'Multiple Records';

    END IF;

    --dbms_output.put_line('Final Count: ' || p_rc%ROWCOUNT);

    /* Close Cursor - We don't close the Cursor here as we want to use this cursor as an OUT Parameter outside of this Proc */
    CLOSE p_rc;

  EXCEPTION
          WHEN OTHERS THEN
            pv_status  :='E';
            pv_message := sqlcode||'-->'||sqlerrm;
            dbms_output.put_line('STATUS: ' || pv_status);
            dbms_output.put_line('MESSAGE: ' || pv_message);
            CLOSE p_rc;

  END test_dynamic_sql;

BEGIN

  g_jobid     :=    null;
  g_last_name :=    'Loch';

  test_dynamic_sql(pv_last_name => g_last_name,
                    p_rc        => g_rc,      /* Out Parameter */
                    pv_jobid    => g_jobid,
                    pv_status   => g_status,  /* Out Parameter */
                    pv_message  => g_message, /* Out Parameter */
                    pv_q        => g_SQL      /* Out Parameter */
                  );

  /* Output OUT Variables aka Provide Output to User */
  dbms_output.put_line('STATUS: '  || g_status);
  dbms_output.put_line('MESSAGE: ' || g_message);

  IF (g_rc IS NOT NULL) THEN
    dbms_output.put_line('We have something here. Fetching Data Now:');

    OPEN g_rc FOR g_sql USING g_last_name, g_jobid;

    LOOP    
      FETCH g_rc INTO g_rec_employee;
      EXIT WHEN g_rc%NOTFOUND; 
      /* Print the Job ID just to show it is working */
      dbms_output.put_line('Job_ID: ' || g_rec_employee.id || ' is the id');      
    END LOOP;

    dbms_output.put_line('Total of: '|| g_rc%ROWCOUNT || ' records Fetched.');

    CLOSE g_rc;
  ELSE
    dbms_output.put_line('null');
  END IF;



EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.put_line('Error '||TO_CHAR(SQLCODE)||': '||SQLERRM);
    CLOSE g_rc;

END;

以上内容与我对这个 SO 问题的第一个答案中的员工表数据相同。

【讨论】:

  • @SKG 我上面的答案尽可能接近原始代码,但是为了再次打开 CURSOR 某些仅在 test_dynamic_sql() Proc 中使用的变量需要被拉出并全球宣布。我们可以改为将员工记录声明为 OUT 参数。在我看来,将员工记录声明为 OUT 参数是要采取的路线,因为这样可以避免在 CALL 之外再次打开游标。但是,对于产生多条记录的调用,我们需要使用 employees TABLE/Collection。
  • 我的问题终于解决了。这是完美的答案。
  • 完美。我很乐意提供帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多