Объектно-реляционная СУБД PostgreSQL. Учебное пособие
.pdfЧаще всего аргументы функции являются входными параметрами, однако PostgreSQL допускает также использование в списке аргументов изменяемых и выходных параметров. Для наглядности программист может перед именем аргумента указать одно из ключевых слов IN (для входного параметра), OUT (для выходного параметра) или INOUT (для изменяемого параметра), однако необходимости в этом нет. Заметим только, что если функция имеет не один, а несколько выходных параметров, то в качестве типа возвращаемого значения следует указывать тип record.
Список аргументов может содержать только названия типов, тогда для ссылки на переданные значения в теле функции используется номер аргумента. Пример:
CREATE FUNCTION square(float, float) RETURNS float AS $$
SELECT $1 * $2; $$ LANGUAGE SQL;
Для улучшения читаемости программы в списке аргументов рекомендуется указывать и имена аргументов, тогда их (имена) можно будет использовать для доступа к переданным значениям:
CREATE FUNCTION square(x float, y float)
RETURNS float AS $$ SELECT x * y;
$$ LANGUAGE SQL;
Замечание. Предположим, что мы сначала реализовали первый вариант функции, а затем решили изменить её, оставив ту же сигнатуру функции (количество аргументов и порядок их типов). При запуске второго запроса мы получим сообщение об ошибке: «функция "square" с аргументами таких типов уже существует». Чтобы избежать появления ошибки, следует указать CREATE OR REPLACE (создать или заменить) в заголовке функции:
CREATE OR REPLACE FUNCTION square(…)
В этом случае новая функция заменит старую с тем же именем. Напомним, что если оставить то же имя, но изменить сигнатуру функции, то будет создана новая функция с тем же именем (этот эффект называется перегрузкой функции).
Функции расширяют возможности SQL-запросов. Так, например, с помощью функции можно реализовывать запросы с параметрами. В этом
51
случае параметры запроса будут передаваться через список аргументов функции.
Приведём пример. Создадим таблицу Города(Название, ЧислоЖителей) и заполним её данными о численности жителей в крупных городах России.
CREATE TABLE Города
(
Название varchar(20) PRIMARY KEY, ЧислоЖителей integer
);
INSERT INTO Города VALUES ('Москва', 11980),
('Санкт-Петербург', 5028), ('Казань', 1176),
('Самара', 1172), ('Волгоград', 1019), ('Саратов', 840), ('Тольятти', 719);
Значение поля ЧислоЖителей в таблице задано в тысячах человек. Напишем функцию, возвращающую Названия и ЧислоЖителей городов с
населением не менее N тысяч человек.
CREATE OR REPLACE FUNCTION sitypeoples (N integer) RETURNS TABLE (Название varchar(20),
ЧислоЖителей integer) AS $$ SELECT Название, ЧислоЖителей FROM Города WHERE ЧислоЖителей >= N;
$$ LANGUAGE SQL
Чтобы увидеть результат, выполним запрос:
SELECT * FROM sitypeoples(1000);
В панель вывода будет выведена информация о городах с числом жителей не менее миллиона человек (рис. 15).
52
Рис. 15. Результат выполнения запроса
В тех случаях, когда возможностей языка SQL не хватает для решения задачи, пользователь может написать свою функцию на одном из языковых расширений PostgreSQL. Обычно используется расширение PL/pgSQL, которое является полноценным языком программирования. Функции на языке PL/pgSQL кроме SQL-операторов могут содержать описания переменных и управляющие конструкции языка.
3.2. Особенности языка PL/pgSQL. Объявление переменных
Тело функции, написанной на PL/pgSQL, представляет собой блок. Структура блока:
[<<метка>>]
[DECLARE объявление переменных] BEGIN
операторы тела блока END [метка];
Каждое объявление и каждый оператор в блоке должны завершаться символом ";" (точка с запятой). Тело блока может представлять собой просто последовательность операторов, а может быть разбито на внутренние блоки. Любой внутренний блок также должен иметь точку с запятой после END, как показано выше. Однако основной блок, представляющий собой тело функции, точки с запятой не требует.
Метка нужна, чтобы идентифицировать блок в операторе досрочного выхода EXIT, или чтобы иметь возможность ссылаться на переменные, объявленные в этом блоке.
53
Ключевые слова нечувствительны к регистру символов. Как и в обычных SQL командах, идентификаторы неявно преобразуются к нижнему регистру, если они не взяты в двойные кавычки.
В коде PL/pgSQL, так же как и в SQL-операторах, можно использовать комментарии двух видов:
–однострочные комментарии: двойной дефис (--) начинает комментарий, который завершается в конце строки.
–блочные комментарии: начинаются с /* и завершаются */. Блочные комментарии могут быть вложенными.
Для объявления переменных используется раздел DECLARE. Заметим, что в PostgreSQL каждая переменная объявляется отдельно.
Пример.
DECLARE
переменная тип [NOT NULL] [ DEFAULT значение];
…
Пример. Пусть требуется объявить переменные i, j, k типа integer. Вариант DECLARE i, j, k integer; вызовет сообщение об ошибке:
ОШИБКА: отсутствует определение типа данных.
Правильный вариант:
DECLARE
i integer; j integer; k integer;
Рекомендуется для лучшего понимания программы объявление каждой переменной записывать в отдельную строку.
В набор типов данных входят как встроенные типы, с которыми мы уже знакомы, так и типы, определённые пользователем. Кроме того, имя каждой таблицы базы данных PostgreSQL рассматривает как имя составного типа, структура которого совпадает со структурой строки таблицы. Для задания такого типа используется ключевое слово %ROWTYPE. Для ссылки на имя
54
поля в переменной типа tablename%ROWTYPE используется символ точка (.) по аналогии с составными структурами в языках программирования. Также можно объявлять переменные, тип которых соответствует полю таблицы. Для этого используется ключевое слово %TYPE.
Для иллюстрации изложенного напишем функцию f_test, которая получает табельный номер сотрудника и возвращает значение атрибута ФИО этого сотрудника.
CREATE OR REPLACE FUNCTION f_test(num integer) RETURNS text AS $$
DECLARE
S Персонал%ROWTYPE;
NПерсонал.ФИО%TYPE;
BEGIN
SELECT * INTO S FROM Персонал WHERE ТабНомер = num;
N= S.ФИО;
RETURN N; END
$$ LANGUAGE plpgsql;
В разделе DECLARE можно инициализировать переменную сразу же при её объявлении, используя инструкцию DEFAULT или синтаксис оператора присваивания.
Пример.
DECLARE
N integer DEFAULT 10;
sum real := 0; -- или sum real = 0;
Чтобы объявить массив элементов какого-либо типа, к имени типа дописывается одна или несколько пар открывающих и закрывающих квадратных скобок. Одновременно с объявлением массива можно задать значения его элементов. Варианты инициализации массива приведены в следующем примере:
55
DECLARE
y integer[] DEFAULT ARRAY[3,4,5]; или y integer[] = ARRAY[3,4,5];
или y integer[] = '{1, 2, 3}';
или y integer[] = '[0:2]={1, 2, 3}';
Вдвух первых вариантах для инициализации массива используется конструктор массива ARRAY. Выражение ARRAY[3,4,5] создаёт одномерный массив из трёх элементов и инициализирует его значениями y[1] = 3, y[2] = 4, y[3] = 5 (нумерация элементов массива по умолчанию начинается с единицы).
Втретьем и четвёртом варианте для инициализации массива используется текстовый литерал специального вида. В третьем варианте задаются только значения элементов массива, нумерация элементов начинается с единицы. В четвёртом варианте кроме значений элементов массива явно задаётся диапазон изменения индекса. Диапазон индексов может начинаться с любого целого числа, в том числе и отрицательного. Количество значений в диапазоне индексов должно равняться количеству элементов массива.
Впрограммном коде массив можно расширить, добавив в него новые элементы. Для одномерного массива это можно сделать, пользуясь оператором присваивания.
Приведём пример. Напишем функцию, в которой создадим массив y как ARRAY[3,4,5], а в теле функции с помощью оператора присваивания добавим в него ещё два элемента: y[0] = 2; y[7] = -1; Полученный массив вернём из функции.
CREATE OR REPLACE FUNCTION test() RETURNS integer[] AS $$
DECLARE
y integer[] = ARRAY[3,4,5]; BEGIN
y[0] = 2; y[7] = -1;
RETURN y; -- или RETURN y[0:7]; END
$$ LANGUAGE plpgsql;
56
Выведем результат вызова функции на панель вывода:
SELECT test();
Получим строку [0:7]={2, 3, 4, 5, NULL, NULL, NULL, -1} в случае возврата всего массива y, или строку {2, 3, 4, 5, NULL, NULL, NULL, -1} в случае возврата среза массива y[0:7], где значениями NULL обозначены неинициализированные элементы массива.
Многомерные массивы PostgreSQL рассматривает как массив массивов, в котором все подмассивы имеют одинаковую длину. При создании многомерных массивов с помощью конструктора можно оставить только внешний ARRAY, так, например, ARRAY[ARRAY[3,-4,12], ARRAY[1,7,0]] эквивалентно ARRAY[[3,-4,12], [1,7,0]]. Для задания многомерного массива также можно использовать текстовый литерал вида '{{список значений подмассива_1}, {список значений подмассива_2}, …}' с указанием или без указания диапазонов индексов. Пример:
DECLARE
A integer[][] = '{{3,-4,12},{1,7,0}}';
B integer[][] = '[3:4][-2:0]={{3,-4,12},{1,7,0}}';
Оба массива содержат одни и те же элементы. Но в массиве A будет стандартная нумерация ([1:2] для строк и [1:3] для столбцов), а в массиве B – заданная программистом ([3:4]для строк и [-2:0] для столбцов).
PostgreSQL содержит целый ряд операций и функций для работы с массивами. К массивам можно применять операции сравнения: =, <>, <, <=, >, >=, а, следовательно, записи можно отсортировать по полю типа массив.
При упорядочивании массивов сначала сравниваются первые элементы, затем вторые, третьи и т.д. по аналогии с лексикографическим упорядочиванием строк.
Операции (идентификаторы A и B обозначают имена массивов): A[i] доступ к элементу одномерного массива,
A[i1][i2]…[ik] доступ к элементу многомерного массива,
A[n:m] |
срез одномерного массива, |
|
A[n1:m1][n2:m2]…[nk:mk] |
срез многомерного массива, |
|
A <@ B |
проверка вхождения массива A в массив B, |
|
A @> B |
проверка вхождения массива B в массив A, |
|
57
A && B возвращает true, если массивы A и B имеют хотя бы один общий элемент,
A || B слияние (конкатенация) двух массивов или массива и значения. Пример. Покажем, как можно расширить многомерный массив с
использованием оператора конкатенации:
CREATE OR REPLACE FUNCTION test() RETURNS integer[][] AS $$ DECLARE
y integer[][] = ARRAY[[1,3],[2,4],[3,5]];
BEGIN
y = y || ARRAY[[4,6]]; RETURN y;
END
$$ LANGUAGE plpgsql; SELECT test();
Возвращаемый результат – строка {{1,3},{2,4},{3,5},{4,6}}
3.3. Функции для работы с массивами
Проиллюстрируем использование функций на примере двух двумерных массивов:
A integer[][] = '{{3,-4,12},{1,7,0}}';
B integer[][] = '[3:4][-2:0]={{3,-4,12},{1,7,0}}';
Текущие размеры и индексацию массива можно получить с помощью функции array_dims, которая возвращает в виде текстовой строки диапазоны изменения индекса для каждой размерности:
array_dims(A) возвращает строку [1:2][1:3] array_dims(B) возвращает строку [3:4][-2:0]
Такой формат вывода ограничивает возможность программного использования получаемых данных, однако очень удобен для вывода отладочной информации.
Чтобы получить числовые значения нижних и верхних границ диапазонов индексов можно использовать функции array_lower и array_upper,
58
которые возвращают соответственно нижнюю и верхнюю границу для указанной размерности. Для массива B получим:
array_lower(B,1) равно 3; array_upper(B,1) равно 4; array_lower(B,2) равно -2; array_upper(B,2) равно 0;
Функция array_length(массив, номер_размерности) возвращает число элементов в указанной размерности массива. Например, для двумерного массива B выражение array_length(B,1) возвращает 2 – число его строк, а выражение array_length(B,2) возвращает 3 – число его столбцов.
Функция unnest(массив) превращает массив в столбец данных. Эта функция позволяет применять к массиву стандартные агрегатные функции, например:
SELECT AVG(w) FROM unnest (ARRAY[1,2,3,4,5]) As w;
В запросе вычисляется среднее по значениям столбца w, который формирует функция unnest (имя столбца указывается в самом запросе). Результат – число 3
– выводится в вещественном виде с шестнадцатью нулями. Для форматирования вывода можно использовать приведение типа:
SELECT AVG(w)::numeric(5,2)
FROM unnest (ARRAY[1,2,3,4,5]) As w;
3.4. Управляющие конструкции языка PL/pgSQL
Оператор присваивания. Выше мы уже пользовались оператором присваивания, который имеет следующие варианты
левая_часть := выражение; левая_часть = выражение;
В левой части оператора присваивания может быть простая переменная, элемент массива или весь массив, поле записи или вся запись. Тип выражения должен совпадать с типом левой части. При необходимости можно использовать преобразование типов, хотя в некоторых случаях преобразование типов выполняется автоматически.
59
Пример. |
|
|
x = |
random(); |
– x – переменная типа float |
i = |
(10*x)::int; |
– i – переменная типа integer |
или i = CAST (10*x AS int);
В данном примере вещественное число 10*x преобразуется к ближайшему целому значению и записывается в переменную i. Заметим, что для преобразования вещественного числа к целому можно использовать встроенные функции trunc(x), floor(x), ceil(x), round(x), которые явно определяют результат преобразования:
trunc(x) – отбрасывает дробную часть x;
floor(x) – возвращает наибольшее целое число, меньшее или равное x; ceil(x) – возвращает наименьшее целое число, большее или равное x; round(x) – возвращает целое число, ближайшее к x.
Пример:
SELECT |
|
trunc(-3.27), |
– выводит -3; |
floor(-3.27), |
– выводит -4; |
ceil(9.84), |
– выводит 10; |
round(9.84); |
– выводит 10; |
В следующем примере выполняется конкатенация (соединение) текстовой строки и числа:
SELECT 'x = ' || 2.71;
Результатом будет текстовая строка 'x = 2.71'. Можно использовать и явное преобразование типов:
SELECT ' x = ' || 2.71::text;
SELECT ' x = ' || CAST(2.71 AS text);
Оператор ветвления. Этот оператор позволяет в зависимости от истинности или ложности некоторого условия выбрать один из двух вариантов выполнения кода программы. Синтаксис оператора:
60
