699
.pdfПодзапрос в примере раздела «Решение» возвращает минимальный ID для каждого значения NAME, представляющий строку, которая не будет удалена:
select min(id) from dupes group by
name
Результат:
MIN(ID)
2
1
5
4
Затем команда DELETE удаляет из таблицы все строки с ID, не возвращенные подзапросом (в данном случае 3, 6 и 7). Если возникают трудности с пониманием того, что происходит, сначала выполните подзапрос и включите NAME в список SELECT:
select name, min(id) from dupes group by name
Результат: NAME MIN(ID) DYNAMITE 2 NAPOLEON 1 SEA SHELLS 5 SHE SELLS 4
Подзапрос возвращает строки, которые будут сохранены. Предикат NOT IN в выражении DELETE обусловливает удаление всех остальных строк.
Удаление записей, на которые есть ссылки в другой таблице
Требуется удалить из одной таблицы записи, на которые ссылается другая таблица. Рассмотрим таблицу DEPT_ACCIDENTS, в которой содержится по одной строке для каждого несчастного случая, произошедшего на производстве. Каждая строка включает номер отдела, в котором имело место происшествие, а также род происшествия.
201
create table dept_accidents ( deptno integer, accident_name varchar(20) )
insert into dept_accidents values (10,'BROKEN FOOT')
insert into dept_accidents values (10,'FLESH WOUND')
insert into dept_accidents values (20,'FIRE')
insert into dept_accidents values (20,'FIRE')
insert into dept_accidents values (20,'FLOOD')
insert into dept_accidents values (30,'BRUISED GLUTE')
select * from dept_accidents DEPTNO ACCIDENT_NAME
Результат:
10 BROKEN FOOT
10 FLESH WOUND
20 FIRE
20 FIRE
20 FLOOD
30 BRUISED GLUTE
Стоит задача удалить из таблицы EMP записи служащих, работающих в отделе, в котором произошло три или более несчастных случая.
Чтобы найти отделы с тремя или более несчастными случаями, используйте подзапрос и агрегатную функцию COUNT. Затем удалите записи всех служащих, работающих в этих отделах:
delete from emp
where deptno in ( select deptno from dept_accidents
group by deptno
having count(*) >= 3 )
202
Подзапрос определит, в каких отделах произошло три или более несчастных случая:
select deptno
from dept_accidents group by deptno having count(*) >= 3
Результат: DEPTNO
20
DELETE удалит записи о всех служащих отделов, возвращенных подзапросом (в данном случае только 20-го отдела).
2.27.Статический SQL
ВPL/SQL допускается включать готовые SQLвыражения непосредственно в код. В таком случае проверка выражения на корректность осуществляется уже при компиляции кода. Так, например, если используемая в запросе таблица не существует, то ошибка будет выдана уже на этапе компиляции.
Запрос одной строки из базы данных
Используется SQL-выражение SELECT, дополненное предложением INTO, в котором указываются переменные, куда запишутся запрошенные данные. Количество и тип этих переменных должны соответствовать количеству (до версии Oracle 9 включительно переменных могло быть больше) и типу полей (хотя при определенных несоответствиях типов может произойти их неявное приведение).
Вслучае, если запрос вернул нулевое число строк, выбрасывается исключение NO_DATA_FOUND. В случае, если строк больше, чем одна, выбрасывается исключение TOO_MANY_ROWS. Эти исключения, вообще говоря, следует обрабатывать в соответствующей части блока, за исключением случаев, когда предполагается, что они не мо-
203
гут быть выброшены. Например, при запросе данных из таблицы по их первичному ключу обработчик исключения
TOO_MANY_ROWS не нужен.
DECLARE |
|
empname VARCHAR2(200); |
|
BEGIN |
|
SELECT ename |
|
INTO empname |
|
FROM scott.emp |
|
WHERE empno = 7439; |
|
EXCEPTION |
|
WHEN NO_DATA_FOUND THEN |
records |
DBMS_OUTPUT.put_line('No |
|
found!'); |
|
WHEN TOO_MANY_ROWS THEN |
more |
DBMS_OUTPUT.put_line('Found |
|
than one string!'); |
|
END; |
|
Запрос нескольких строк из базы данных и использование курсора
Для запроса нескольких строк следует использовать курсоры PL/SQL. Под курсором подразумевается указатель на очередную строку в результатах запроса. Открытие и закрытие курсора осуществляется операторами OPEN и CLOSE. Считывание значений, на которые указывает курсор, и его перевод на следующую строку осуществляется оператором FETCH.
Считывание данных из запроса оформляется как цикл. Когда курсор достигает конца результатов запроса, очередной вызов оператора FETCH не считает новых данных, аатрибут <имя_курсора>%NOTFOUND принимает значение TRUE. Этособытиеиспользуетсядляпрерыванияработы цикла.
Обработчиков исключений в этом случае не требуется; если данные не будут найдены, то цикл не будет выполнен ни разу.
204
DECLARE
empname VARCHAR2(200); CURSOR c1 IS
SELECT ename FROM scott.emp;
BEGIN OPEN c1; LOOP
FETCH c1 INTO empname; EXIT WHEN c1%NOTFOUND;
-- работа со значением empname END LOOP;
CLOSE c1; END;
Использование указателей на курсоры
Для большей гибкости удобно вместо курсора использовать указатель на курсор с разными курсорами. В таком случае курсор с запросом определяются неявно при вызове операции OPEN для указателя на курсор с помощью предложения FOR. Один указатель на курсор можно использовать со многими курсорами и, соответственно, со многими запросами.
DECLARE
TYPE GenericCursor IS REF CURSOR; с1 GenericCursor;
empname VARCHAR2(200); BEGIN
OPEN c1 FOR SELECT ename FROM scott.emp;
LOOP
FETCH c1 INTO empname; EXIT WHEN c1%NOTFOUND;
-- работа со значением empname END LOOP;
CLOSE c1; END;
205
Использование связанных переменных
Как при использовании курсоров, так и при использовании указателей на курсоры рекомендуется при формировании запросов не включать в них конкретные константы. Следует использовать связанные переменные (англ. bind variables), т.е. переменные непосредственно в теле запроса, значения которых будут подставляться только при открытии курсора для запроса. Связанные переменные обозначаются именем, предваренным символом двоеточия. При открытии курсора значения переменных указываются с помощью предложения USING.
При первом выполнении функции, приведенной ниже, запрос будет разобран в СУБД, для него будет создан план выполнения (это будет происходить сравнительно долго). При последующих выполнениях функции будет использоваться уже созданный план выполнения, и запрос будет быстро возвращать значения.
FUNCTION get_employee_name (empid INTEGER, empcity VARCHAR2) RETURN VARCHAR2 IS
TYPE GenericCursor IS REF CURSOR; c1 GenericCursor;
empname VARCHAR2(200); BEGIN
OPEN c1 FOR 'SELECT ename FROM employees WHERE id =:id AND city =:city' USING empid, empcity;
-- цикл не используется, так как запрос вернет не более одной строки
FETCH c1 INTO empname; CLOSE c1;
RETURN empname;
END get_employee_name;
206
Неявное определение курсора в цикле
Иногда вместо того, чтобы объявлять курсор или указатель на него, удобно воспользоваться неявным определением курсора и неявным определением переменной типа запись (RECORD):
DECLARE BEGIN
FOR rec IN (SELECT id, ename, 1 AS VALUE FROM employees) LOOP
DBMS_OUTPUT.put_line(rec.id || ': ' || rec.ename);
END LOOP; END;
Пакетный запрос многих строк
При запросе большого числа строк можно увеличить производительность, если вместо поочередного зачитывания строк результата зачитать их все сразу, тем самым значительно снизив количество переключений контекста от PL/SQL к SQL и обратно. Для пакетного чтения необходимо снабдить оператор FETCH инструкцией BULK COLLECT. Данные при этом должны записываться не в переменные, а в ассоциативные коллекции:
DECLARE
TYPE GenericCursor IS REF CURSOR; c1 GenericCursor;
TYPE VarcharTable IS TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
--объявили тип данных "Таблица строк", элементы которой нумеруются числами
empnames VarcharTable;
--объявили переменную созданного
типа
BEGIN
207
OPEN c1 FOR SELECT ename FROM employees;
FETCH c1 BULK COLLECT INTO empnames; CLOSE c1;
END;
Выполнение операций DML
Операции DML, как правило, выполняются точно так же, как и в SQL:
DECLARE BEGIN
UPDATE employees SET hire_date = SYSDATE WHERE id != 1;
INSERT INTO employees (name, city) VALUES ('SMITH', 'Тикси');
COMMIT;
END;
2.28. Динамический SQL
Динамический запрос
Для большей гибкости часто статические запросы заменяются запросами, формируемыми динамически. Недостаток динамического SQL в том, что динамические запросы, разумеется, не могут быть проверены на этапе компиляции. Если, например, используемой в запросе таблицы не существует, то при выполнении операции OPEN возникнет исключение.
Классическая задача, требующая применения динамического конструирования SQL-запросов, – отчеты в интерфейсах, где пользователь может выбрать разные условия, по которым следует сформировать отчет.
Ниже приведен блок кода, который в зависимости от некоего условия запрашивает имя сотрудника либо по ключу, либо по городу.
208
DECLARE
TYPE GenericCursor IS REF CURSOR; c1 GenericCursor;
sel VARCHAR2(4000); bind_var VARCHAR2(200); result VARCHAR2(200);
BEGIN
sel:= 'SELECT name FROM employees WHERE 1 = 1';
IF... THEN
sel:= sel || ' AND id =:1'; bind_var:= 12;
ELSE
sel:= sel || ' AND city =:1'; bind_var:= 'Магадан';
END IF;
OPEN c1 FOR sel USING bind_var;
FETCH c1 INTO result;
CLOSE c1;
END;
Выполнение динамических операций DML и DDL EXECUTE IMMEDIATE
Динамические операции DML и DDL выполняются с помощью оператора EXECUTE IMMEDIATE.
DECLARE
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM employees';
EXECUTE IMMEDIATE 'DROP TABLE employees';
-- COMMIT или ROLLBACK не нужен, по-
тому что DDL-операция завершила транзакцию
END;
209
Допускается использование связанных переменных, их значения также указываются в предложении USING [20].
2.29. Хранимые процедуры. Вызов процедуры
Определение процедуры
Процедуры в модуле SQL определяются в следующем синтаксисе:
<procedure>::=
PROCEDURE <procedure name> <parameter declaration>...; <SQL statement>;
<parameter declaration>::= <parameter name> <data type> |<SQLCODE parameter>
<SQLCODE parameter>::= SQLCODE <SQL statement>::=
<close statement> |<commit statement>
|<delete statement positioned> |<delete statement searched> |<fetch statement>
|<insert statement> |<open statement> |<rollback statement> |<select statement>
|<update statement positioned> |<update statement searched>
Имена всех процедур в одном модуле должны быть различны. Любое имя параметра, содержащегося в операторе SQL-процедуры, должно быть специфицировано в разделе объявления параметров. Число фактических параметров при вызове процедуры должно совпадать с числом формальных параметров, указанных при ее объявлении [14].
210