15 Занятие 15: PL/pgSQL / Массивы
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dev1_15_plpgsql_arrays.html Archive: DEV1-handouts-16.zip Страниц PDF: 24
Page 1: PL/pgSQL / Массивы
Авторские права
© Postgres Professional, 2017–2024 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Массивы и работа с ними в PL/pgSQL
- Циклы по элементам массивов
- Функции с переменным числом параметров и полиморфные функции
- Использование массивов в столбцах таблиц
Page 3: Тип массива
Массив
- набор пронумерованных элементов одного и того же типа
- одномерные, многомерные
- Создание
- Использование
- элементы как скалярные значения
- срезы массива
- операции с массивами: сравнение, вхождение, пересечение,
- конкатенация, использование с ANY и ALL вместо подзапроса, …
Комментарии преподавателя
Массив, как и составной тип (запись), не является скалярным и состоит из нескольких элементов другого типа. Но, в отличие от записей, а) все эти элементы имеют одинаковый тип и б) обращение к ним происходит не по имени, а по целочисленному индексу (здесь индекс понимается в математическом смысле, а не как индекс БД).
Тип массива не надо специально объявлять, поскольку при создании любого типа (в том числе таблицы) создается также тип массива элементов этого типа с именем _тип. При объявлении переменной-массива достаточно добавить квадратные скобки к имени типа элементов.
Массив является полноценным типом SQL, его можно использовать как любой другой тип: создавать столбцы таблиц этого типа, использовать его для параметров функций и т. п. Элементы массива могут использоваться как обычные скалярные значения. Можно использовать и срезы (slice) массивов.
Массивы можно сравнивать, проверять на неопределенность, искать вхождение элемента и находить пересечение с другими массивами, конкатенировать и пр. Также массивы можно использовать по аналогии с подзапросами в конструкциях ANY/SOME и ALL.
https://postgrespro.ru/docs/postgresql/16/arrays
Различные функции для работы с массивами можно найти в справочном материале, прилагаемом к курсу.
Page 4: Инициализация и обращение к элементам
Объявление переменной и инициализация массива целиком:
Одномерный массив можно заполнять и поэлементно — при необходимости он автоматически расширяется. Если пропустить какие-то элементы, они получают неопределенные значения. Что будет выведено?
Поскольку нумерация началась не с единицы, перед самим массивом дополнительно выводится диапазон номеров элементов. Мы можем определить составной тип и создать массив из элементов этого типа:
Еще один способ получить массив — создать его из подзапроса:
Page 5: Page 5
Можно и наоборот, массив преобразовать в таблицу:
Интересно, что выражение IN со списком значений преобразуется в поиск по массиву:
Двумерный массив — прямоугольная матрица, память под которую выделяется при инициализации. Литерал выглядит как массив массивов, имеющих одинаковое число элементов. Здесь мы использовали другой способ инициализации — с помощью символьной строки. После инициализации многомерный массив уже нельзя расширить.
Page 6: Массивы и циклы
Обычный цикл по индексам элементов
- array_lower
- array_upper
- Цикл FOREACH по элементам массива
- проще, но индексы элементов недоступны
Комментарии преподавателя
Для итерации по элементам массива вполне можно использовать обычный целочисленный цикл FOR, используя функции, возвращающие минимальный и максимальный индексы массива.
Однако есть и специализированный вариант цикла: FOREACH. В таком варианте переменная цикла пробегает не индексы элементов, а сами элементы. Поэтому переменная должна иметь тот же тип, что и элементы массива (как обычно, если элементами являются записи, то одну переменную составного типа можно заменить несколькими скалярными переменными).
Тот же цикл с фразой SLICE позволяет итерировать срезы массива. Например, для двумерного массива одномерными срезами будут его строки.
https://postgrespro.ru/docs/postgresql/16/plpgsql-control-structures#PLPGS QL-FOREACH-ARRAY
Page 7: Массивы и циклы
Цикл можно организовать, итерируя индексы элементов массива. Второй параметр функций array_lower и array_upper — номер размерности (единица для одномерных массивов).
Если индексы не нужны, то проще итерировать сами элементы:
Итерация индексов в двумерном массиве:
Итерация элементов двумерного массива не требует вложенного цикла:
Page 8: Page 8
Существует также возможность выполнять в подобном цикле итерацию по срезам, а не по отдельным элементам. Значение SLICE в такой конструкции должно быть целым числом, не превышающим размерность массива, а переменная, куда читаются срезы, сама должна быть массивом. В примере — цикл по одномерным срезам:
Page 9: Массивы и подпрограммы
Подпрограммы с переменным числом параметров
- все необязательные параметры должны иметь одинаковый тип
- передаются в подпрограмму в виде массива
- последний параметр-массив определяется с режимом VARIADIC
- Полиморфные подпрограммы
- работают со значениями разных типов;
- тип конкретизируется во время выполнения
- дополнительные полиморфные псевдотипы anyarray, anynonarray,
- anycompatiblearray и anycompatiblenonarrray
- могут иметь переменное число параметров
Комментарии преподавателя
Массивы позволяют создавать подпрограммы (функции или процедуры) с переменным числом параметров.
В отличие от параметров со значениями по умолчанию, которые при объявлении подпрограммы надо явно перечислить, необязательных параметров может быть сколько угодно и все они передаются подпрограмме в виде массива. Но, как следствие, все они должны иметь один и тот же (или совместимый, в случае использования anycompatible/anycompatiblearray) тип.
При объявлении подпрограммы последним указывается один параметр с режимом VARIADIC, имеющий тип массива.
https://postgrespro.ru/docs/postgresql/16/xfunc-sql#XFUNC-SQL-VARIADIC -FUNCTIONS
Мы уже говорили про полиморфные подпрограммы, которые могут работать с параметрами разных типов. При объявлении подпрограммы указывается специальный полиморфный псевдотип, а конкретный тип уточняется во время выполнения по фактическому типу переданных параметров.
Для массивов есть отдельные полиморфные типы anyarray, anycompatiblearray (и anynonarray, anycompatiblenonarrray для не-массивов).
Эти типы можно использовать совместно с передачей переменного числа аргументов при объявлении VARIADIC-параметра.
https://postgrespro.ru/docs/postgresql/16/xfunc-sql#XFUNC-SQL-POLYMO RPHIC-FUNCTIONS
Page 10: Массивы и подпрограммы
В теме «SQL. Процедуры» мы рассматривали перегрузку и полиморфизм и создали функцию maximum, которая находила максимальное из трех чисел. Обобщим ее на произвольное число аргументов. Для этого объявим один VARIADIC-параметр:
Пробуем:
Для полноты картины и эта функция может быть сделана полиморфной, чтобы принимать любой тип данных (для которого, конечно, должны быть определены операции сравнения).
Полиморфные типы anycompatiblearray и anycompatible (а также anyarray и anyelement) всегда согласованы между собой: anycompatiblearray = anycompatible[], anyarray = anyelement[]; Нам нужна переменная, имеющая тип элемента массива. Но объявить ее как anycompatible нельзя — она должна иметь реальный тип. Здесь помогает конструкция %TYPE.
Проверим:
Page 11: Page 11
Вот теперь у нас получился практически полный аналог выражения greatest!
Page 12: Массив или таблица?
{#source-figure-012 caption=“Source slide page 12: Массив или таблица?”}
Комментарии преподавателя
Классический реляционный подход предполагает, что в таблице хранятся атомарные значения (первая нормальная форма). Язык SQL не имеет средств для «заглядывания внутрь» сложносоставных значений.
Поэтому обычный подход состоит в создании отдельной таблицы, связанной с основной отношением «многие ко многим».
Тем не менее, мы можем создать таблицу со столбцом типа массива. PostgreSQL имеет богатый набор функций для работы с массивами, а поиск элемента в массиве может быть ускорен специальными индексами (такие индексы рассматриваются в курсе DEV2).
Такой подход бывает удобен: получается компактное представление, не требующее соединений. В частности, массивы активно используются в системном каталоге PostgreSQL.
Какое решение выбрать? Зависит от того, какие ставятся задачи, какие требуются операции. Рассмотрим пример.
Page 13: Массив или таблица?
Представим себе, что мы проектируем базу данных для ведения блога. В блоге есть сообщения, и нам хотелось бы сопоставлять им теги. Традиционный подход состоит в том, что для тегов надо создать отдельную таблицу, например, так:
Связываем сообщения и теги отношением многие ко многим через еще одну таблицу:
Наполним таблицы тестовыми данными:
Теперь мы можем вывести сообщения и теги:
Или чуть иначе — возможно удобнее получить массив тегов. Для этого используем агрегирующую функцию:
Page 14: Page 14
Можем найти все сообщения с определенным тегом:
Может потребоваться найти все уникальные теги — это совсем просто:
Теперь попробуем подойти к задаче по-другому. Пусть теги будут представлены текстовым массивом прямо внутри таблицы сообщений.
Теперь у нас нет идентификаторов тегов, но они нам не очень и нужны.
Вывод всех сообщений упростился:
Сообщения с определенным тегом тоже легко найти (используем оператор пересечения &&). Эта операция может быть ускорена с помощью индекса GIN, и для такого запроса не придется перебирать всю таблицу
Page 15: Page 15
сообщений.
А вот получить список тегов довольно сложно. Это требует разворачивания всех массивов тегов в большую таблицу и поиск уникальных значений — тяжелая операция.
Тут хорошо видно, что имеет место дублирование данных. Итак, оба подхода вполне могут применяться. В простых случаях массивы выглядят проще и работают хорошо. В более сложных сценариях (представьте, что вместе с именем тега мы хотим хранить дату его создания; или требуется проверка ограничений целостности) классический вариант становится более привлекательным.
Page 16: Итоги
Массив состоит из пронумерованных элементов одного и того же типа данных
- Столбец с массивом как альтернатива отдельной таблице: удобные операции и индексная поддержка
- Позволяет создавать функции с переменным числом параметров
Page 17: Практика
- Создайте функцию add_book для добавления новой книги.
- Функция должна принимать два параметра —
- название книги и массив идентификаторов авторов —
- и возвращать идентификатор новой книги.
- Проверьте, что в приложении появилась возможность
- добавлять книги.
Page 18: 1. Функция add_book
Page 19: Практика+
- Реализуйте функцию map, принимающую два параметра: массив вещественных чисел и название вспомогательной функции, принимающей один параметр вещественного типа.
- Функция должна возвращать исходный массив, в котором
- к каждому элементу применена вспомогательная функция.
- Реализуйте функцию reduce, принимающую два параметра: массив вещественных чисел и название вспомогательной функции, принимающей два параметра вещественного типа.
- Функция должна возвращать вещественное число,
- полученное последовательной сверткой массива слева
- направо.
- Сделайте функции map и reduce полиморфными.
- Например:
- Например:
В этом случае значение вычисляется как
Page 20: 1. Функция map
INTO a[i] не работает, поэтому нужна отдельная переменная.
Другой вариант реализации с циклом FOREACH:
Page 21: 2. Функция reduce
Greatest (как и least) — не функция, а встроенное условное выражение, поэтому из-за экранирования не получится использовать ее напрямую:
Вместо нее используем реализованную в демонстрации функцию maximum.
Вариант с циклом FOREACH:
Page 22: Page 22
3. Полиморфные варианты функций
Функция map.
Требуется фиктивный параметр типа anyelement, чтобы внутри функции объявить переменную такого же типа.
Page 23: Page 23
Пример вызова с другим типом данных:
Функция reduce.