Шаг 60 - PL/SQL - КУРСОРЫ - Неявные Курсоры

Мы с вами, уже рассмотрели основные концепции определения и работы с курсорами. Но один аспект я упустил. А именно - неявные курсоры. Как вы уже, наверное, догадались, сам оператор SELECT представляет собой в прямом определении "КУРСОР", или еще это называют SQL-курсор! Откуда следует вывод, что любой оператор DML объемлит собой курсор. Так как, каждый оператор DML выполняется в пределах контекстной области и по этому имеет курсор указывающий на контекстную область. В отличии от явных, SQL-курсор не открывается и не закрывается. PL/SQL - сам неявно открывает SQL-курсор, обрабатывает SQL-оператор и закрывает SQL-курсор. Для SQL-курсора операторы FETCH, OPEN, CLOSE не нужны, но с ними можно использовать курсорные атрибуты, вот таким образом:

------ SQL%АТРИБУТ КУРСОРА ---------------

Давайте рассмотрим конкретный пример. В шаге 48 мы закончили работу с табличками PEOPLE и OLD_PEOPLE, если вы их удалили, то ничего страшного, просто вернитесь к шагу 46 и снова их создайте. Если они у вас остались, еще лучше! Итак, рассмотрим такой пример на основе таблички PEOPLE. Пусть нам нужно изменить поле NM в таблице PEOPLE, где поле ID содержит значение 555. Введем такой запрос, перед тем как:

SELECT * FROM PEOPLE
/

Получаем (у меня так, у вас может отличаться, если таблица удалялась или вы делали что-то еще):

SQL> SELECT * FROM PEOPLE
  2  /
  
  ID NM     FM         OT
---- ------ ---------- ---------------
   7 Irvin  Show       Brefovich 
   2 Bob    Jason      Martovich
   3 IVAN   Black      NULL

Но строки, где бы поле ID содержало 555, нет! Это нам и нужно, для того, чтобы продемонстрировать работу атрибута. Итак, запишем блок:

BEGIN

UPDATE PEOPLE
	SET NM = 'Pupkin'
WHERE ID = 555;

	IF(SQL%NOTFOUND) THEN
	INSERT INTO PEOPLE(ID, NM, FM, OT)
		VALUES(555, 'Pupkin', 'Axlamon', 'Feodosovich');
	END IF;

COMMIT;

END;
/

Получаем:

SQL> BEGIN
  2  
  3  UPDATE PEOPLE
  4  	SET NM = 'Pupkin'
  5  WHERE ID = 555;
  6  
  7  	IF(SQL%NOTFOUND) THEN
  8  	INSERT INTO PEOPLE(ID, NM, FM, OT)
  9  		VALUES(555, 'Pupkin', 'Axlamon', 'Feodosovich');
 10  	END IF;
 11  
 12  COMMIT;
 13  
 14  END;
 15  /

Процедура PL/SQL успешно завершена.

Посмотрим, что вышло:

SELECT * FROM PEOPLE
/

Получаем:

SQL> SELECT * FROM PEOPLE
  2  /
  
  ID NM     FM         OT
---- ------ ---------- ---------------
   7 Irvin  Show       Brefovich 
   2 Bob    Jason      Martovich
   3 IVAN   Black      NULL
 555 Pupkin Axlamon    Feodosovich

Как видите, сработал атрибут SQL-курсора, %NOTFOUND. И, так как записи с таким значением поля ID не было, то с помощью оператора INSERT мы его добавили в таблицу PEOPLE! Все получилось верно! То же можно было сделать применив атрибут SQL-курсора, %ROWCOUNT. Вот так:

BEGIN

UPDATE PEOPLE
	SET NM = 'Mirkin'
WHERE ID = 888;

	IF(SQL%ROWCOUNT = 0) THEN
	INSERT INTO PEOPLE(ID, NM, FM, OT)
		VALUES(888, 'Mirkin', 'Lupoglaz', 'Kotletovich');
	END IF;

COMMIT;

END;
/

Получаем:

SQL> BEGIN
  2  
  3  UPDATE PEOPLE
  4  	SET NM = 'Mirkin'
  5  WHERE ID = 888;
  6  
  7  	IF(SQL%ROWCOUNT = 0) THEN
  8  	INSERT INTO PEOPLE(ID, NM, FM, OT)
  9  		VALUES(888, 'Mirkin', 'Lupoglaz', 'Kotletovich');
 10  	END IF;
 11  
 12  COMMIT;
 13  
 14  END;
 15  /

Процедура PL/SQL успешно завершена.

Смотрим содержимое таблицы PEOPLE:

SELECT * FROM PEOPLE
/

Получаем:

SQL> SELECT * FROM PEOPLE
  2  /
  
  ID NM     FM         OT
---- ------ ---------- ---------------
   7 Irvin  Show       Brefovich 
   2 Bob    Jason      Martovich
   3 IVAN   Black      NULL
 555 Pupkin Axlamon    Feodosovich
 888 Mirkin Lupoglaz   Kotletovich

И здесь все сработало верно, с той разницей, что мы применили атрибут SQL-курсора %ROWCOUNT. Теперь, давайте рассмотрим более сложную ситуацию, с применением атрибута SQL-курсора %NOTFOUND. А начнем, вот с чего. Я уже показывал вам оператор SELECT - формы SELECT ... INTO. Но, не заострял внимание. Давайте немного отвлечемся и я все постараюсь объяснить. Итак, SELECT ... INTO, это как бы некий эквивалент явного курсора с применением оператора выборки FETCH. Но сам по себе он является неявным курсором с выборкой в переменную. Понятно? Если нет, идем дальше. Если явный курсор вида:

CURSOR get_people IS
	SELECT * FROM PEOPLE;
	
-- Record to store the fetched data --
v_gt get_people%ROWTYPE;
.
.
.
FETCH get_people INTO v_gt;	

Выбегает данные с помощью оператора FETCH, имеющего конструкцию, INTO в переменную v_gt. В свою очередь, являющейся одномерной коллекцией на основе курсора get_people. То запись вида:

	
-- Record to store the fetched data --
v_gt PEOPLE%ROWTYPE;
   
SELECT * INTO v_gt FROM PEOPLE;

Абсолютно ей эквивалентна. SELECT ... INTO и есть неявный курсор с выборкой данных в одномерную коллекцию v_gt, так как они обе имеют один и тот же тип определения ROWTYPE и содержат четыре переменных вида:

v_gt.ID, v_gt.NM, v_gt.FM, v_gt.OT

С той лишь разницей, что первая объявлена на основе курсора (поля которого определены собственно выражением SELECT), а вторая на основе полей таблицы! Теперь, я надеюсь, что все неясности по поводу курсоров как явных, так и не явных у вас отпали сами собой. Безусловно, явные курсоры наиболее предпочтительны, так как более наглядны и применимы. Но если вам необходимо выполнить что-то очень простое, то можно использовать SELECT ... INTO. В остальных случаях только явные курсоры! :) Итак, собственно переходим к делу, если применить атрибут %NOTFOUND совместно с конструкцией SELECT ... INTO, то он может не сработать, так как если SELECT ... INTO не получит запись, то возникнет ошибка "ORA-1403 no data found". Для этого мы запишем вот такой блок и разберем его:

SET SERVEROUTPUT ON

DECLARE

	m_PLP PEOPLE%ROWTYPE;
	
BEGIN
	
	DBMS_OUTPUT.enable;
	
	SELECT * INTO m_PLP FROM PEOPLE
	WHERE ID = 999;
	
	IF (SQL%NOTFOUND) THEN
	INSERT INTO PEOPLE(ID, NM, FM, OT)
		VALUES(999, 'Volopasov', 'Lobotryas', 'Oslovich');
	END IF;
	
	COMMIT;

EXCEPTION  
	WHEN NO_DATA_FOUND THEN
	DBMS_OUTPUT.put_line('No Data Found! Execute exception handler');	

END;
/

Получаем:

SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
  2  
  3  	m_PLP PEOPLE%ROWTYPE;
  4  
  5  BEGIN
  6  
  7  	DBMS_OUTPUT.enable;
  8  
  9  	SELECT * INTO m_PLP FROM PEOPLE
 10  	WHERE ID = 999;
 11  
 12  	IF (SQL%NOTFOUND) THEN
 13  	INSERT INTO PEOPLE(ID, NM, FM, OT)
 14  		VALUES(999, 'Volopasov', 'Lobotryas', 'Oslovich');
 15  	END IF;
 16  
 17  	COMMIT;
 18  
 19  EXCEPTION
 20  	WHEN NO_DATA_FOUND THEN
 21  	DBMS_OUTPUT.put_line('No Data Found! Execute exception handler');
 22  
 23  END;
 24  /
No Data Found! Execute exception handler                                        

Процедура PL/SQL успешно завершена.

Как видите, сработал блок EXCEPTION (как с ним управляться - это чуть позже). Что и должно было произойти в нашем случае. Так как блок "SELECT * INTO..." не получил ни одной записи, то сработал блок EXCEPTION с условием NO_DATA_FOUND. Вот так производится обработка атрибутов при использовании неявных курсоров.


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Летучий Сергей - 16.11.2003