Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Информационное обеспечение управляющих систем реального времени

..pdf
Скачиваний:
3
Добавлен:
15.11.2022
Размер:
3.63 Mб
Скачать

Подзапрос в примере раздела «Решение» возвращает минимальный 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