Как обработать NO_DATA_FOUND в sys-refcursor, используя переменные связывания - PullRequest
1 голос
/ 21 мая 2019

Как справиться со случаем, когда оператор выбора не возвращает ни одной записи в 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 ]

3 голосов
/ 23 мая 2019

Этот ответ предназначен для решения проблем, возникающих при работе с курсорами ссылки в качестве параметров вывода. Приведенный ниже код вызывает вашу test_dynamic_sql() процедуру. В этом Proc мы OPEN курсор, FETCH данные, на которые он указывает, и мы НЕ CLOSE курсор, поскольку мы немедленно снова используем этот курсор вне процедуры test_dynamic_sql(). ПРИМЕЧАНИЕ - при использовании FETCH курсор больше не будет предоставлять вам данные и должен быть снова открыт. Поскольку ваш курсор использует динамический SQL, мы должны объявить наш динамический «запрос» в том же месте, в котором мы объявляем остальные наши глобальные переменные.

«Курсоры НЕ предназначены для повторного использования: вы читаете их один раз, продолжаете двигаться вперед и, как вы делаете, вы отбрасываете все ранее отсканированные строки». Этот факт был украден из этой SO Post: Oracle. Повторно использовать курсор в качестве параметра в двух процедурах .

Вне этой процедуры мы сначала должны проверить, была ли успешно инициализирована Курсор, используя оператор 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

0 голосов
/ 22 мая 2019

Есть несколько вещей, которые вам не хватает, если вы хотите работать с курсором так, как вы пытаетесь, независимо от неявного Ref Cursor из-за Dynamic SQL или явного Cursor.

Для того, чтобыиспользовать% ROWCOUNT или% NOTFOUND, вы должны сначала получить курсор.Эта ссылка «PL / SQL 101: Понимание курсоров ссылок» , в которой содержится тонна информации по этой теме, но все, что необходимо для ответа на ваш вопрос, - это знать, что сначала вы должны ПОЛУЧИТЬ данные.

Ниже приведено изображение, отображающее данные в моей таблице сотрудников. Обратите внимание, что есть два сотрудника с фамилией «Лох» .

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

...