Объектно-реляционная СУБД PostgreSQL. Учебное пособие
.pdfIF условие THEN операторы
[ELSE
операторы] END IF;
Ветвь ELSE может присутствовать (тогда ветвление называется полным) или отсутствовать в операторе ветвления (тогда ветвление называется неполным). Приведём пример неполного ветвления:
IF k<0 THEN
RAISE NOTICE 'Значение k=% меньше нуля', k; END IF;
Инструкция RAISE NOTICE выводит сообщение, в котором знак процента (спецификация вывода) заменён значением переменной k. В общем случае, вместо k может стоять некоторое выражение или даже список выражений (количество выражений должно быть равным количеству спецификаций вывода).
Условие представляет собой логическое выражение, чаще всего это операции сравнения (простые условия), соединённые логическими операциями AND и/или OR. Заметим, что PostgreSQL «вычисляет» все условия, входящие в логическое выражение, что следует учитывать при написании программы. Результат вычисления логического выражения – значение true («истина») или false («ложь»).
Операции сравнения: =, <, <=, >, >=, <> или != Пример. SELECT 4<5; (выводит t – сокращение от true)
SELECT 4<3; (выводит f – сокращение от false) SELECT 3<4 AND 4<5; (выводит t)
Заметим, что один и даже оба операнда логической операции могут быть равными значению NULL («не определено»). Чтобы заполнить таблицу истинности трёхзначной логики для операций AND и OR, воспользуемся следующим методом. Присвоим TRUE = 1, NULL = 0, FALSE = -1,
тогда A AND B = min(A, B),
61
A OR B = max(A, B). NOT A = -A
Пользуясь этим приёмом, легко определить результат логических операций в трёхзначной логике. Например, TRUE AND NULL = NULL, так как min(1, 0) = 0, TRUE OR NULL = TRUE, так как max(1, 0) = 1.
Ветвления также могут быть вложенными. Дополнительное ветвление можно «вложить» в любую ветвь основного ветвления. Если дополнительные ветвления всегда вкладываются в ветвь «то», то всю конструкцию можно заменить следующим вариантом:
IF условие_1 THEN операторы
ELSIF условие_2 THEN
операторы
… |
|
|
[ |
ELSE |
|
|
операторы] |
-- если все предыдущие условия ложны |
END IF; |
|
|
Заметим, что вместо ELSIF можно писать ELSEIF. Пример.
IF k<n THEN
RAISE NOTICE 'Значение k=% меньше %', k, n; ELSIF k>n THEN
RAISE NOTICE 'Значение k=% больше %', k, n; ELSE
RAISE NOTICE 'Значение k=% и равно %', k, n; END IF;
Оператор выбора. Синтаксис оператора выбора:
CASE выражение
WHEN значение или список значений THEN операторы
WHEN значение или список значений THEN операторы
62
...
[ELSE операторы ]
END CASE;
При входе в оператор выбора вычисляется значение выражения, стоящего после слова CASE. Затем оно последовательно сравнивается со значениями, указанными в предложениях WHEN. При совпадении этих значений выполняются операторы, стоящие после слова THEN, после чего управление передаётся за пределы оператора. Необязательный элемент оператора выбора – предложение ELSE – выполняется, только если значение выражения не совпало ни с одним из значений, указанных в предложениях WHEN.
Второй вариант оператора CASE является более гибким, так как допускает любые условия в каждом из предложений WHEN:
CASE
WHEN условие_1 THEN операторы
WHEN условие_2 THEN операторы
...
[ ELSE операторы ]
END CASE;
В отличие от оператора IF PostgreSQL допускает использование оператора CASE внутри SQL-запроса. Приведём пример такого запроса.
Пусть в базе данных УчебныйПроцесс имеется таблица Ведомость с полями НомерЗачётки, КодДисциплины и Оценка, содержащая результаты экзаменов, и таблица Дисциплины, которая содержит Код и Название дисциплин.
Требуется для каждой дисциплины подсчитать количество пятёрок, четвёрок, троек и двоек, полученных на экзаменах, и вывести результаты в виде перекрёстной таблицы. Заметим, что перекрёстных запросов (таких как запросы с PIVOT в MS Access), в PostgreSQL нет, однако, если число столбцов в
63
перекрёстном запросе заранее известно, то такой запрос можно сконструировать в виде обычного SQL-запроса. Покажем, как это сделать.
Создадим и заполним таблицы базы данных. А) таблица Дисциплины:
CREATE TABLE Дисциплины ( Код int PRIMARY KEY, Название text
);
Б) таблица Ведомость:
CREATE TABLE Ведомость ( НомерЗачётки int, КодДисциплины int,
Оценка text,
PRIMARY KEY(НомерЗачётки, КодДисциплины), FOREIGN KEY(КодДисциплины) REFERENCES Дисциплины
);
Заполним эти таблицы данными и выполним запрос
INSERT INTO Дисциплины
VALUES(1, 'Математический анализ'), (2, 'Линейная алгебра'), (3, 'Основы информатики');
INSERT INTO Ведомость
VALUES(11203, 1,'отл'), (11203, 2,'хор'), (11203, 3,'хор'), (11221, 1,'уд'), (11221, 2,'хор'), (11221, 3,'уд'), (11300, 1,'хор'), (11300, 2,'отл'), (11300, 3,'отл');
SELECT Название, sum(CASE Оценка
WHEN 'отл' THEN 1 ELSE 0 END) As "Оценка 5",
64
sum(CASE Оценка
WHEN 'хор' THEN 1 ELSE 0 END) As "Оценка 4", sum(CASE Оценка
WHEN 'уд' THEN 1 ELSE 0 END) As "Оценка 3", sum(CASE Оценка
WHEN 'неуд' THEN 1 ELSE 0 END) As "Оценка 2" FROM Ведомость, Дисциплины
WHERE Ведомость.КодДисциплины = Дисциплины.Код GROUP BY Название
В запросе создаются пять столбцов. В первый столбец выводятся названия экзаменационных дисциплин, в остальные четыре – количество пятёрок, четвёрок, троек и двоек, полученных на экзаменах по каждому предмету. Для этого агрегатная функция sum суммирует данные, которые вычисляет (и возвращает!) оператор CASE.
Результат выполнения запроса представлен на рис. 16.
Рис. 16. Результат выполнения «перекрёстного» запроса
Операторы цикла
1. Бесконечный цикл.
[<<label>>] LOOP
операторы
END LOOP [label];
Для выхода из такого цикла приходится использовать оператор EXIT – оператор досрочного выхода из цикла:
EXIT [label] [WHEN условие];
65
или
IF условие THEN EXIT [label]; END IF;
Заметим, что EXIT можно использовать и в циклах других типов. 2. Цикл с условием (цикл «пока»).
[<<label>>]
WHILE условие LOOP операторы
END LOOP [label];
3. Цикл с параметром (цикл «для»).
[<<label>>]
FOR параметр IN [REVERSE] диапазон [BY шаг] LOOP
операторы
END LOOP [label];
Диапазон определяется двумя «крайними» значениями A и B (записывается как A .. B). A и B могут быть целыми числами или целочисленными выражениями. Ключевое слово REVERSE используется, если параметр цикла должен изменяться от B к A, при его отсутствии параметр будет меняться от A до B. Величина шага (всегда положительное число!) задаётся после ключевого слова BY (при отсутствии BY шаг параметра цикла берётся равным 1).
Цикл с параметром часто применяется при работе с массивами.
Пример. Вычислим сумму элементов для каждой строки двумерного массива A размером 3×2. Используем для этого вложенные циклы:
FOR i IN 1..3 LOOP R[i] = 0;
FOR j IN 1..2 LOOP R[i]=R[i]+A[i][j];
END LOOP; END LOOP;
В общем случае диапазоны изменения индекса можно указать, используя встроенные функции array_lower() и array_upper():
FOR i IN array_lower(A,1)..array_upper(A,1) LOOP
66
R[i] = 0;
FOR j IN array_lower(A,2)..array_upper(A,2) LOOP R[i]=R[i]+A[i][j];
END LOOP;
END LOOP;
4. Цикл по массиву (цикл «для каждого») позволяет обрабатывать элементы массива без использования индексов.
[<<label>>]
FOREACH элемент [SLICE размерность] IN ARRAY массив
LOOP операторы
END LOOP [label];
Если ключевое слово SLICE отсутствует, то из массива, какой бы размерности он ни был, последовательно извлекаются значения его элементов. Если SLICE присутствует, то из массива извлекаются подмассивы указанной размерности (например, строки из двумерного массива, если указано SLICE 1). В любом случае, переменная «элемент» должна иметь соответствующий тип. В большинстве случаев цикл FOREACH удобнее, чем цикл FOR. Приведём решение предыдущего примера с использованием цикла FOREACH:
i=0;
FOREACH S SLICE 1 IN ARRAY A LOOP i=i+1;
R[i]=(SELECT sum(elem) FROM unnest(S) AS elem);
END LOOP;
В отличие от предыдущего варианта, нам не потребовалось явно указывать количество строк и элементов в строке массива A. Кроме того, за счёт введения переменной S типа integer[], для вычисления суммы элементов строки удалось применить агрегатную функцию sum().
5. Цикл по запросу.
Цикл по запросу просматривает построчно результат выполнения запроса SELECT. Синтаксис цикла:
[<<label>>]
FOR элемент IN запрос LOOP
операторы
END LOOP [label];
67
Если запрос возвращает строку из нескольких значений разных типов, то тип переменной «элемент» должен совпадать с типом возвращаемой строки. При объявлении переменной можно воспользоваться ключевым словом %ROWTYPE (если возвращается целиком строка одной таблицы) или указать для этой переменной тип RECORD. Тип RECORD соответствует записи в Паскале, но в отличие от Паскаля является более абстрактным, т.е., можно объявить переменную такого типа и «залить» в неё произвольную структуру данных.
Пример. Объявим переменную R типа RECORD и последовательно запишем в неё разные значения
DECLARE
RRECORD;
BEGIN
R= (1, 'one'); RAISE NOTICE '%', R;
R= ('один', 1, ARRAY[3,5]);
RAISE NOTICE '%', R;
END
Оформим этот фрагмент кода как функцию и запустим её. В панели вывода получим сообщения, показанные на рис. 17.
Рис. 17. Вывод сообщений NOTICE в панель вывода
Переменной типа RECORD можно пользоваться для работы со строками таблицы. Приведём пример. Пусть у нас есть таблица Данные, содержащая поле Номер, которое является первичным ключом, и поле Числа, которое является массивом целых чисел. Напишем функцию, которая находит сумму максимальных элементов по каждому массиву (полю Числа).
CREATE OR REPLACE FUNCTION SumMax()
RETURNS int AS $$
DECLARE
68
R RECORD; S int:=0;
BEGIN
FOR R IN SELECT * FROM Данные LOOP S = S + (SELECT max(elem)
FROM unnest(R.Числа) As elem);
END LOOP;
RETURN S; END
$$ LANGUAGE plpgsql;
и вызовем её в запросе SELECT SumMax();
Вданном случае переменную R можно было бы объявить как переменную типа Данные%ROWTYPE, поскольку запрос SELECT в коде программы возвращает целиком строку таблицы Данные.
3.5.Примеры функций на языках SQL и PL/pgSQL
Вэтом разделе приведём примеры написания функций на языках SQL и PL/pgSQL. Сравнивая их, можно понять, в каких случаях достаточно использовать язык SQL, а в каких необходимо привлечь язык программирования PL/pgSQL.
Создадим таблицу Коробки (Номер, Размеры), где Размеры – массив из трёх целых чисел, задающих длину, ширину и высоту коробки. Заметим, что тип int[] не задаёт ограничений на количество элементов в поле Размеры, поэтому формально массив Размеры может иметь любую длину.
CREATE TABLE Коробки
(
Номер int PRIMARY KEY,
Размеры int[]
);
1. Напишем функцию, которая получает Номер коробки и возвращает её объём: А) на языке SQL: функция V(k) реализует запрос с параметром; она получает Номер коробки, который используется в предложении WHERE для
отбора нужной записи.
69
CREATE OR REPLACE FUNCTION V(k int) RETURNS int AS $$
RETURN (SELECT Размеры[1]*Размеры[2]*Размеры[3] FROM Коробки WHERE Номер = k);
$$ LANGUAGE SQL;
Б) на языке PL/pgSQL: по существу функция осталась прежней, изменился (и то незначительно) синтаксис записи её тела.
CREATE OR REPLACE FUNCTION V(k int) RETURNS int AS $$
BEGIN
RETURN (SELECT Размеры[1]*Размеры[2]*Размеры[3] FROM Коробки WHERE Номер = k);
END
$$ LANGUAGE plpgsql;
Вызвать написанную функцию можно для нескольких коробок сразу:
SELECT V(k) FROM generate_series(1, 4) AS k;
2. Подсчитаем число коробок, объём которых не превосходит некоторого значения V. Приведём решение на языке SQL как более простое, при этом воспользуемся уже написанной функцией вычисления объёма коробки.
CREATE OR REPLACE FUNCTION lessV(V int)
RETURNS bigint AS $$
SELECT count(*) FROM Коробки
WHERE V(Номер)<=V; $$ LANGUAGE SQL;
Замечания:
–тип возвращаемого значения должен совпадать с типом агрегатной функции count(), которая возвращает значение типа bigint;
–выражение V(Номер)<V является корректным, так как слева стоит вызов функции с именем V, а справа – переменная с тем же именем.
3. Напишем функцию, которая получает значение V и возвращает массив целых чисел – номера коробок, объём которых не меньше этого значения.
CREATE OR REPLACE FUNCTION moreV(V int)
RETURNS int[] AS $$
BEGIN
RETURN ARRAY(SELECT Номер
70
