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

How to handle NO_DATA_FOUND in sys-refcursor using bind variables

select语句在SYS_REFCURSOR中没有return任何记录如何处理? 我正在使用使用绑定变量的动态 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 异常,但它也不起作用。

我需要 return 状态为 'S'、'E'、'NR'

谢谢!

如果您希望以尝试的方式使用游标而不考虑由于动态 SQL 或显式游标引起的隐式 Ref Cursor,则您缺少一些东西。

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

下图显示了我的 Employees table 中的数据。 请注意,有两名员工姓 'Loch'

The below Code is all within it's own anonymous block but can easily be converted into a Procedure/Function. It has all of your required Status's and Messages. In order to handle searches that have more than one result I added an additional Status/Message to tell the user that Multiple Records were returned. Last, just to make it easier to work with your code I took out all but two of your Parameters. NOTE: If the Procedure's Parameters are all passed in as NULL the Dynamic SQL That is generated will query the entire table as it basically removes all of the Filters in the WHERE clause.

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;

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

"Cursors are NOT designed to be re-used: you read them once, keep moving forward and as you're doing so you're discarding any previously scanned rows." This fact was stolen from this SO Post: Oracle. Reuse cursor as parameter in two procedures.

除此过程外,我们首先必须通过使用 IF 语句检查 Cursor 是否存在来检查 Cursor 是否已成功初始化: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 问题的第一个回答中相同的员工 table 数据。