понедельник, 14 января 2013 г.

Курсоры в Oracle

Курсор это средство извлечения данных из БД.
Курсоры содержат определения столбцов и объектов.
Курсоры используются для получения строк, возвращаемых запросом.
Для передачи строк в курсор используется запрос, после чего вы можете
выбирать строки из курсора по одной за один раз.
 

create table t1(id, type, text)
as
select object_id, object_type, object_name
from all_objects;

или так

create table t1
as
select object_id id, object_type type, object_name text
from all_objects;


select id, type, text from t1
where id =17367

17367    SCHEDULE    FILE_WATCHER_SCHEDULE



select id, type, text from t1
where type = 'SCHEDULE';

17364    SCHEDULE    DAILY_PURGE_SCHEDULE
17367    SCHEDULE    FILE_WATCHER_SCHEDULE
17372    SCHEDULE    PMO_DEFERRED_GIDX_MAINT_SCHED
18172    SCHEDULE    BSLN_MAINTAIN_STATS_SCHED



Неявные курсоры определяются в момент выполнения:

DECLARE
    v_text t1.text%TYPE;

BEGIN
    SELECT text INTO v_text
    FROM t1
    WHERE id = 17367;
    DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
END;
/


text = FILE_WATCHER_SCHEDULE

В ходе выполнения кода создается курсор для выборки значения text.



Явный курсор определяется до начала выполнения:

DECLARE
    CURSOR c_get_text
    IS
    SELECT text
    FROM t1
    WHERE id = 17367;

    v_text t1.text%TYPE;

BEGIN
    OPEN c_get_text;
    FETCH c_get_text INTO v_text;
    DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
    CLOSE c_get_text;
END;
/

text = FILE_WATCHER_SCHEDULE



Преимущество явного курсора заключается в наличии у него атрибутов,
облегчающих применение условных операторов.



CREATE OR REPLACE PROCEDURE proc1
AS
    CURSOR c_get_text
    IS
    SELECT text
    FROM t1
    WHERE id = 17367;

    v_text t1.text%TYPE;

BEGIN
    OPEN c_get_text;
    FETCH c_get_text INTO v_text;
    IF  c_get_text%NOTFOUND THEN
        DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
    ELSE
        DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
    END IF;
    CLOSE c_get_text;
END;
/

BEGIN
    proc1;
END;
/

text = FILE_WATCHER_SCHEDULE


А как подобное сделать с неявным курсором:


CREATE OR REPLACE PROCEDURE proc2
AS
    v_text t1.text%TYPE;
    v_bool BOOLEAN := TRUE;

BEGIN
    BEGIN
        SELECT text INTO v_text
        FROM t1
        WHERE id = 17367;

    EXCEPTION
        WHEN no_data_found THEN
            v_bool := FALSE;
        WHEN others THEN
            RAISE;
    END;

    IF NOT v_bool THEN
       DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
    ELSE
       DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
    END IF;
END;
/


BEGIN
    proc2;
END;
/


text = FILE_WATCHER_SCHEDULE


Пришлось заниматься перехватом исключений, чтобы определить, была ли найдена запись.


Параметризация курсоров помогает повысить степень их повторного использования.

курсор с параметром:

DECLARE
    CURSOR c_get_text(par1 NUMBER)
    IS
    SELECT text
    FROM t1
    WHERE id = par1;

    v_text t1.text%TYPE;

BEGIN
    OPEN c_get_text(17367);
    FETCH c_get_text INTO v_text;
    DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
    CLOSE c_get_text;
END;
/

text = FILE_WATCHER_SCHEDULE



Переменные типа REF CURSOR могут ссылаться на любые реальные курсоры.
Программа, использующая тип REF CURSOR, может работать с курсорами,
не заботясь о том, какие конкретно данные будут извлечены ими во время выполнения.


CREATE OR REPLACE PROCEDURE proc_ref
AS
    v_curs SYS_REFCURSOR;
    v_text t1.text%TYPE;

BEGIN
    OPEN v_curs
    FOR
    'SELECT text '
    || 'FROM t1 '            
    || 'WHERE id = 17367';

    FETCH v_curs INTO v_text;

    DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );

    CLOSE v_curs;
END;
/


BEGIN
    proc_ref;
END;
/


text = FILE_WATCHER_SCHEDULE


Во время компиляции Oracle не знает, каким будет тексе запроса, - он видит строковую переменную.
Но наличие типа REF CURSOR говорит ему о том, что надо будет обеспечить некую работу с курсором.


Например я могу создать функцию, которая принимает некий входной параметр, создает курсор и возвращает тип REF CURSOR :


CREATE OR REPLACE FUNCTION func1(par1 NUMBER)
RETURN SYS_REFCURSOR
IS
    v_curs SYS_REFCURSOR;

BEGIN
    OPEN v_curs
    FOR
    'SELECT text '
    || 'FROM t1 '            
    || 'WHERE id = '
    || par1;

    RETURN v_curs;
END;
/




Другой пользователь может воспользоваться этой функцией так:

DECLARE

    v_curs SYS_REFCURSOR;
    v_text t1.text%TYPE;

BEGIN
    v_curs := func1(17367);

    FETCH v_curs INTO v_text;

    IF  v_curs%NOTFOUND THEN
        DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
    ELSE
        DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
    END IF;

    CLOSE v_curs;
END;
/

text = FILE_WATCHER_SCHEDULE



Для пользователя, вызывающего функцию  func1(), она для него представляет черный ящик, возвращающий курсор.



Сильнотипизированный и слаботипизированный REF CURSOR.

TYPE имя_типа_курсора IS REF CURSOR [ RETURN возвращаемый_тип ];


например:


TYPE refcursor IS REF CURSOR RETURN table1%ROWTYPE;

TYPE refcursor IS REF CURSOR;

Первая форма  REF CURSOR называется сильно типизированной, поскольку тип структуры,
возвращаемый курсорной переменной, задается в момент объявления
(непосредственно или путем привязки к типу строки таблицы).

Вторая форма (без предложения RETURN) называется слаботипизированной.
Тип возвращаемой структуры данных для нее не задается.
Такая курсорная переменная обладает большей гибкостью, поскольку для нее можно задавать любые запросы
с любой структурой возвращаемых данных.

В Oracle 9i появился предопределенный слабый тип REF CURSOR с именем SYS_REFCURSOR,
теперь можно не определять собственный слабый тип, достаточно использовать стандартный тип Oracle:

DECLARE
    my_cursor SYS_REFCURSOR;




Пример сильнотипизированного курсора:


DECLARE

    TYPE my_type_rec IS RECORD (text t1.text%TYPE);
    TYPE my_type_cur IS REF CURSOR RETURN my_type_rec;
    v_curs my_type_cur;
    v_text t1.text%TYPE;

BEGIN
    OPEN v_curs
    FOR
    SELECT text
     FROM t1            
     WHERE id = 17367;

    FETCH v_curs INTO v_text;

    DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );

    CLOSE v_curs;
END;
/

text = FILE_WATCHER_SCHEDULE


или так:

DECLARE

    TYPE my_type_cur IS REF CURSOR RETURN t1%ROWTYPE;
    v_curs  my_type_cur;
    v_var   t1%ROWTYPE;

BEGIN
    OPEN v_curs
    FOR
    SELECT *
     FROM t1            
     WHERE id = 17367;

    FETCH v_curs INTO v_var;

    DBMS_OUTPUT.PUT_LINE( 'id = ' || v_var.id || ', type = ' || v_var.type || ', text = ' || v_var.text );

    CLOSE v_curs;
END;
/


id = 17367, type = SCHEDULE, text = FILE_WATCHER_SCHEDULE





Пример слаботипизированного курсора:


DECLARE

    TYPE my_type_cur IS REF CURSOR;
    v_curs my_type_cur;
    v_text t1.text%TYPE;

BEGIN
    OPEN v_curs
    FOR
    SELECT text
     FROM t1            
     WHERE id = 17367;

    FETCH v_curs INTO v_text;

    DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );

    CLOSE v_curs;
END;
/


text = FILE_WATCHER_SCHEDULE


или так:

DECLARE

    v_curs SYS_REFCURSOR;
    v_text t1.text%TYPE;

BEGIN
    OPEN v_curs
    FOR
    SELECT text
     FROM t1            
     WHERE id = 17367;

    FETCH v_curs INTO v_text;

    DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );

    CLOSE v_curs;
END;
/

text = FILE_WATCHER_SCHEDULE



Курсор можно передавать в качестве параметра:

1. Функция принимающая курсор

CREATE OR REPLACE FUNCTION get_cursor(p_curs SYS_REFCURSOR)
RETURN VARCHAR2
IS
    v_text t1.text%TYPE;

BEGIN

    FETCH p_curs INTO v_text;

    IF  p_curs%NOTFOUND THEN
        DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
    ELSE
        DBMS_OUTPUT.PUT_LINE( 'Данные найдены. ' );
    END IF;

    RETURN v_text;
END;
/


2. Процедура принимающая текст SQL

CREATE OR REPLACE PROCEDURE get_sql (p_sql VARCHAR2)
IS
    v_curs SYS_REFCURSOR;
    v_res  VARCHAR2(50);
BEGIN
    IF v_curs%ISOPEN THEN
        CLOSE v_curs;
    END IF;
    BEGIN
        OPEN v_curs FOR p_sql;
    EXCEPTION
        WHEN OTHERS THEN
              RAISE_APPLICATION_ERROR(-20000, 'Unable to open cursor');
    END;
    v_res := get_cursor(v_curs);
    CLOSE v_curs;
    DBMS_OUTPUT.PUT_LINE(v_res);
END;
/


Запускаем так:

BEGIN
    get_sql( 'SELECT text FROM t1 WHERE id = 17367' );
END;
/

Данные найдены.
FILE_WATCHER_SCHEDULE


Ещё примеры:

SET SERVEROUTPUT ON

DECLARE

  -- Объявляем переменые

  var1      tab.col1%TYPE;
  var2      tab.col2%TYPE;
  var3      tab.col3%TYPE;

  -- Объявляем курсор

  CURSOR cur IS
    SELECT col1, col1, col3
    FROM tab
    ORDER BY col1;

BEGIN
  -- Открываем курсор

  OPEN cur;

  LOOP
    -- Выбираем из курсора строки
    FETCH cur
    INTO var1, var2, var3;

    EXIT WHEN cur%NOTFOUND;

    -- Выводим значения переменных
    DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var1 || ', col2 = ' || var2 || ', col3 = ' || var3 );
  END LOOP;

  -- Закрываем курсор
  CLOSE cur;
END;
/



Курсоры и цикл FOR

Для получения доступа к строкам из курсора можно использовать цикл FOR.
При использовании цикла FOR не нужно явно открывать курсор - цикл FOR сделает это автоматически.


SET SERVEROUTPUT ON

DECLARE

  CURSOR cur IS
    SELECT col1, col1, col3
    FROM tab
    ORDER BY col1;

BEGIN
  FOR var IN cur LOOP
    DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var.col1 || ', col2 = ' || var.col2 || ', col3 = ' || var.col3 );
  END LOOP;
END;
/



Выражение OPEN - FOR

С курсором можно использовать выражение OPEN - FOR, которое добавляет еще больше гибкости при обработке курсоров,
поскольку вы можете назначить курсор для другого запроса.
Запрос может быть любым корректным выражением SELECT.
Это означает что вы можете повторно использовать курсор и назначить курсору позже в коде другой запрос.

SET SERVEROUTPUT ON

DECLARE

  -- Определим тип REF CURSOR
  TYPE t_cur IS
  REF CURSOR RETURN tab%ROWTYPE;


  -- Определим объект типа  t_cur
  cur t_cur;

  -- Определим объект для хранения столбцов из таблицы tab
  var tab%ROWTYPE;

BEGIN
  -- назначим запрос для объекта cur и откроем его
  OPEN cur FOR
  SELECT * FROM tab WHERE col1 < 5;

  -- Выбираем строки из cur в var
  LOOP
    FETCH cur INTO var;
    EXIT WHEN cur%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var.col1 || ', col2 = ' || var.col2 || ', col3 = ' || var.col3 );
  END LOOP;

  -- Закрываем объект cur
  CLOSE cur;
END;
/




Все ранее рассмотренные курсоры имели конкретный возвращаемый тип, который должен совпадать
со столбцами в запросе исполняемом курсором.
Можно определить курсор, который не имеет возвращаемого типа и может исполнять любой запрос.

SET SERVEROUTPUT ON

DECLARE
  -- Определим тип REF CURSOR
  TYPE t_cur IS REF CURSOR;

  -- Определим объект типа  t_cur
  cur t_cur;

  -- Определим объект для хранения столбцов из таблицы tab1
  var1 tab1%ROWTYPE;

 -- Определим объект для хранения столбцов из таблицы tab2
  var2 tab2%ROWTYPE;

BEGIN
  -- назначим запрос для объекта cur и откроем его
  OPEN cur FOR
  SELECT * FROM tab1 WHERE col1 < 5;

  -- Выбираем строки из cur в var1
  LOOP
    FETCH cur INTO var1;
    EXIT WHEN cur%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var1.col1 || ', col2 = ' || var1.col2 || ', col3 = ' || var1.col3 );
  END LOOP;

  -- назначим новый запрос для объекта cur и откроем его
  OPEN cur FOR
  SELECT * FROM tab2 WHERE col1 < 3;

  -- Выбираем строки из cur в var2
  LOOP
    FETCH cur INTO var2;
    EXIT WHEN cur%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var2.col1 || ', col2 = ' || var2.col2 || ', col3 = ' || var2.col3 );
  END LOOP;

  -- Закрываем объект cur
  CLOSE cur;
END;
/