如何使用绑定变量在sys-refcursor中处理NO_DATA_FOUND

时间:2019-05-21 19:49:06

标签: oracle plsql sys-refcursor

当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->错误(发生任何错误时)
  • NR->没有记录(0条记录)

谢谢!

2 个答案:

答案 0 :(得分:3)

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

  

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

在此过程之外,我们首先必须通过使用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问题的第一个答案中的雇员表数据相同。

Employees Table and Data

答案 1 :(得分:0)

如果由于动态SQL或显式Cursor而希望隐式Ref Cursor,则希望以尝试的方式使用游标时,会缺少一些内容。

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

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

Employees Table and Data

  

以下代码位于其自己的匿名块中,但可以轻松转换为过程/函数。它具有您所有必需的状态和消息。为了处理具有多个结果的搜索,我添加了一个附加的状态/消息以告知用户已返回多个记录。最后,为了简化代码的使用,我删除了除两个参数之外的所有参数。 注意:如果将过程的参数全部作为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;

enter image description here