11 Занятие 11: PL/pgSQL / Обзор и конструкции языка
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dev1_11_plpgsql_introduction.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
- Структура блока, объявление переменных
- Анонимные блоки
- Подпрограммы на языке PL/pgSQL
- Условные операторы и циклы
- Вычисление выражений
Page 3: История PL/pgSQL
Появился в версии 6.4 в 1998 году
- устанавливается по умолчанию с версии 9.0
- Цели создания
- простой язык для написания пользовательских функций и триггеров
- добавить управляющие структуры к языку SQL
- сохранить возможность использования любых пользовательских типов,
- функций и операторов
- Родословная: Oracle PL/SQL, Ада
Комментарии преподавателя
PL/pgSQL — один из первых процедурных языков в PostgreSQL. Он впервые появился в 1998 году в версии 6.4, а с версии 9.0 стал устанавливаться по умолчанию при создании базы данных.
PL/pgSQL дополняет SQL, предоставляя такие возможности процедурных языков, как использование переменных и курсоров, условные операторы, циклы, обработка ошибок и т. д.
PL/pgSQL проектировался на основе языка Oracle PL/SQL, а тот, в свою очередь, был создан на основе подмножества языка Ада. Эта ветвь происходит от таких языков, как Алгол и Паскаль. Большинство современных языков программирования принадлежат другой ветви — ветви Си-подобных языков, поэтому PL/pgSQL может показаться непривычным, избыточно многословным (характерное отличие — использование ключевых слов BEGIN и END вместо фигурных скобок). Впрочем, этот синтаксис отлично сочетается с синтаксисом SQL.
https://postgrespro.ru/docs/postgresql/16/plpgsql-overview
Page 4: Структура блока
Метка блока
- Объявления переменных
- область действия — блок
- область видимости может перекрываться вложенными блоками,
- но можно использовать метку блока
- любые типы SQL, ссылки на типы объектов (%TYPE)
- Операторы
- управляющие конструкции
- операторы SQL, кроме служебных
- вложенные блоки
- Обработка исключительных ситуаций
Комментарии преподавателя
Операторы языка PL/pgSQL объединяются в блоки. В структуре блока можно выделить:
● Необязательную метку, которую можно использовать для устранения неоднозначности в именовании.
● Необязательную секцию, предназначенную для объявления локальных переменных и курсоров. В качестве типа можно указать любой тип, определенный в SQL. Можно также сослаться на тип столбца таблицы с помощью конструкции %TYPE.
● Основную секцию исполнения, в которой располагаются операторы.
● Необязательную секцию обработки исключительных ситуаций.
В качестве операторов можно использовать как команды PL/pgSQL, так и большинство команд SQL, то есть этих два языка интегрированы практически бесшовно. Нельзя использовать служебные команды SQL, такие, как VACUUM. А команды управления транзакциями (например, COMMIT, ROLLBACK) допускаются только в процедурах.
В качестве оператора может выступать и другой (вложенный) PL/pgSQL-блок.
https://postgrespro.ru/docs/postgresql/16/plpgsql-structure
https://postgrespro.ru/docs/postgresql/16/plpgsql-declarations#PLPGSQL-D ECLARATION-TYPE
Page 5: Анонимные блоки
Разовое выполнение процедурного кода
- без создания хранимой подпрограммы
- без возможности передать параметры
- без возможности вернуть значение
- Оператор DO языка SQL
Комментарии преподавателя
Для использования PL/pgSQL не обязательно создавать подпрограммы. Код на PL/pgSQL можно оформить и выполнить как анонимный блок, при помощи SQL-команды DO.
Эту команду можно использовать с разными серверными языками, но если язык не указать явно, то будет использоваться PL/pgSQL.
Код анонимного блока не сохраняется на сервере. Также нет возможности передать в анонимный блок параметры или вернуть из него значение. Хотя косвенно это можно сделать, например, через таблицы.
https://postgrespro.ru/docs/postgresql/16/sql-do
Page 6: Анонимные блоки
Общая структура блока PL/pgSQL:
Все секции, кроме секции операторов, являются необязательными. Минимальный блок PL/pgSQL-кода:
Вариант программы Hello, World!
После BEGIN точка с запятой не ставится! Переменные могут иметь модификаторы: CONSTANT — значение переменной не должно изменяться после инициализации; NOT NULL — не допускается неопределенное значение.
Пример вложенных блоков. Переменная во внутреннем блоке перекрывает переменную из внешнего блока, но с помощью меток можно обратиться к любой из них:
Page 7: Page 7
Page 8: Подпрограммы PL/pgSQL
Заголовок подпрограммы не зависит от языка
- имя, входные и выходные параметры
- для функций: возвращаемое значение, категория изменчивости
- Указание LANGUAGE plpgsql
- Возврат значений
- оператор RETURN
- присвоение значений выходным (INOUT, OUT) параметрам
Комментарии преподавателя
Мы уже познакомились с хранимыми функциями и процедурами на примере языка SQL. Большинство рассмотренных вопросов, связанных с созданием и управлением подпрограммами, относится и к подпрограммам на PL/pgSQL:
● создание, изменение, удаление подпрограмм;
● расположение в системном каталоге (pg_proc);
● параметры;
● возвращаемое значение и категории изменчивости (для функций);
● перегрузка и полиморфизм;
● и т. д.
Если в SQL подпрограмма возвращала значение, выданное последним SQL-оператором, то в подпрограммах на PL/pgSQL требуется либо присваивать возвращаемые значения формальным INOUT-и OUT-параметрам, либо (для функций) использовать специальный оператор RETURN.
Page 9: Подпрограммы PL/pgSQL
Пример функции, возвращающей значение с помощью оператора RETURN:
Та же функция, но с OUT-параметром. Возвращаемое значение присваивается параметру:
Та же функция, но с INOUT-параметром. Такой параметр используется и для принятия входного значения, и для возврата значения функции:
Page 10: Условные операторы
IF
- стандартный условный оператор
- CASE
- похож на CASE языка SQL, но не возвращает значение
Внимание: трехзначная логика
- условие должно быть истинно; false и NULL не подходят
Комментарии преподавателя
PL/pgSQL предлагает два условных оператора: IF и CASE.
Первый — совершенно стандартный оператор, имеющийся во всех языках.
Второй похож на конструкцию CASE в языке SQL, но это именно оператор, он не возвращает значение. Это некий аналог оператора switch в C или Java.
Важно всегда помнить о том, что логические выражения в SQL (и, следовательно, в PL/pgSQL) могут принимать три значения: true, false и NULL. Условие срабатывает, только когда оно истинно, и не срабатывает, когда ложно или не определено. Это одинаково верно и для условий WHERE в SQL, и для условных операторов PL/pgSQL.
https://postgrespro.ru/docs/postgresql/16/plpgsql-control-structures#PLPGS QL-CONDITIONALS
Page 11: Условные операторы
Общий вид оператора IF:
Секция ELSIF может повторяться несколько раз, а может отсутствовать. Секция ELSE может отсутствовать. Выполняются операторы, соответствующие первому истинному условию. Если ни одно из условий не истинно, выполняются операторы ELSE (если есть). Пример функции, использующей условный оператор для форматирования номера телефона. Функция возвращает значение составного типа:
Общий вид оператора CASE (первый вариант — по условию):
Секция WHEN может повторяться несколько раз. Секция ELSE может отсутствовать. Выполняются операторы, соответствующие первому истинному условию. Если ни одно из условий не истинно, выполняются операторы ELSE (отсутствие ELSE в таком случае — ошибка). Пример использования:
Page 12: Page 12
Общий вид оператора CASE (второй вариант — по выражению):
Секция WHEN может повторяться несколько раз. Секция ELSE может отсутствовать. Выполняются операторы, соответствующие первому истинному условию «выражение = значение». Если ни одно из условий не истинно, выполняются операторы ELSE (отсутствие ELSE в таком случае — ошибка). При однотипных условиях эта форма CASE может оказаться компактней:
Page 13: Циклы
Цикл FOR по диапазону чисел
- Цикл WHILE с предусловием
- Бесконечный цикл
- Цикл может иметь метку, как блок
- Управление
- выход из цикла (EXIT)
- переход на новую итерацию (CONTINUE)
Комментарии преподавателя
Для повторного выполнения набора операторов PL/pgSQL предлагает несколько видов циклов:
● цикл FOR по диапазону целых чисел;
● цикл WHILE с предусловием;
● бесконечный цикл.
Цикл представляет собой разновидность блока и может иметь собственную метку.
Выполнением цикла можно дополнительно управлять, инициируя переход на новую итерацию или выход из цикла.
https://postgrespro.ru/docs/postgresql/16/plpgsql-control-structures#PLPGS QL-CONTROL-STRUCTURES-LOOPS
Цикл FOR может работать не только по диапазону чисел, но и по результатам выполнения запроса и по массивам. Эти варианты цикла FOR будут рассмотрены в следующих темах.
Page 14: Циклы
В PL/pgSQL все циклы используют общую конструкцию:
К ней может добавляться заголовок, определяющий условие выхода из цикла. Цикл по диапазону FOR повторяется, пока счетчик цикла пробегает значения от нижней границы до верхней. С каждой итерацией счетчик увеличивается на 1 (но инкремент можно изменить в необязательной фразе BY).
Переменная, выступающая счетчиком цикла, объявляется неявно и существует только внутри блока LOOP — END LOOP. При указании REVERSE значение счетчика на каждой итерации уменьшается, а нижнюю и верхнюю границы цикла нужно поменять местами:
Пример использования цикла FOR — функция, “переворачивающая” строку:
Напомним, что строгая (STRICT) функция немедленно возвращает NULL, если хотя бы один из входных параметров не определен. Тело функции при этом не выполняется. Цикл WHILE выполняется до тех пор, пока истинно условие:
Та же функция, обращающая строку, с использованием цикла WHILE:
Page 15: Page 15
Цикл LOOP без заголовка выполняется бесконечно. Для выхода используется оператор EXIT:
Метка необязательна; если не указана, будет прерван самый вложенный цикл. Фраза WHEN также необязательна; при отсутствии цикл прерывается безусловно. Пример использования цикла LOOP:
Тело функции помещается в неявный блок, метка которого совпадает с именем функции. Поэтому к параметрам можно обращаться как «имя_функции.параметр». Убедимся, что все функции работают правильно:
Замечание. В PostgreSQL есть встроенная функция reverse. Иногда бывает полезен оператор CONTINUE, начинающий новую итерацию цикла:
Page 16: Вычисление выражений
Любое выражение вычисляется в контексте SQL
- выражение автоматически преобразуется в запрос
- запрос подготавливается
- переменные PL/pgSQL подставляются как параметры
- Особенности
- можно использовать все возможности SQL, включая подзапросы
- невысокая скорость выполнения,
- хотя разобранный запрос (и, возможно, план запроса) кешируются
- неоднозначности при разрешении имен требуют внимания
Комментарии преподавателя
Выражения, встречающиеся в PL/pgSQL-коде, вычисляются с помощью SQL-запросов к базе данных. Интерпретатор сам строит нужный запрос, формируя оператор вида SELECT
С одной стороны, такой подход не способствует скорости работы PL/pgSQL, зато обеспечивает теснейшую интеграцию с SQL. Фактически, в выражениях можно использовать любые возможности SQL без ограничений, включая вызов встроенных и пользовательских функций, выполнение подзапросов и т. п.
Начиная с версии PostgreSQL 14 оптимизировано выполнение простых выражений (как минимум не содержащих обращений к таблицам) — такие выражения обрабатываются синтаксическим анализатором сервера напрямую, без обращений к планировщику.
https://postgrespro.ru/docs/postgresql/16/plpgsql-expressions
Page 17: Вычисление выражений
Выражения в PL/pgSQL выполняются SQL-движком. Таким образом, в PL/pgSQL доступно ровно то, что доступно в SQL. Например, если в SQL можно использовать конструкцию CASE, то точно такая же конструкция будет работать и в коде на PL/pgSQL (в качестве выражения; не путайте с оператором CASE … END CASE, который есть только в PL/pgSQL):
В выражениях можно использовать и подзапросы:
Еще пример с вычислением выражения в PL/pgSQL — сколько функций «обращения» строк у нас в результате получилось?
Page 18: Итоги
PL/pgSQL — доступный по умолчанию, интегрированный с SQL, удобный и простой в использовании язык
- Управление подпрограммами на PL/pgSQL не отличается от работы с подпрограммами на других языках
- DO — команда SQL для выполнения анонимного блока
- Переменные PL/pgSQL могут использовать любые типы SQL
- Язык поддерживает обычные управляющие конструкции, такие как условные операторы и циклы
Page 19: Практика
- Измените функцию book_name так, чтобы длина возвращаемого значения не превышала 45 символов. Если название книги при этом обрезается, оно должно завершаться на троеточие.
- Проверьте реализацию в SQL и в приложении; при необходимости добавьте книг с длинными названиями. 2. Снова измените функцию book_name так, чтобы избыточно длинное название обрезалось по границе целого слова (укорачивание по словам).
- Проверьте реализацию.
Комментарии преподавателя
- Например:
Вот некоторые случаи, которые имеет смысл проверить:
● длина названия меньше 45 символов (не должно измениться);
● длина названия ровно 45 символов (не должно измениться);
● длина названия 46 символов (от названия должны быть отрезаны 4 символа, т. к. добавятся еще три точки).
Лучше всего написать и отладить отдельную функцию укорачивания, которую затем использовать в book_name. Это полезно и по другим соображениям:
● такая функция может пригодиться где-то еще;
● каждая функция будет выполнять ровно одну задачу.
- Например:
Как поведет себя ваша реализация, если название состоит из одного длинного слова без пробелов?
Page 20: 1. Укорачивание названия книги
Напишем более универсальную функцию, принимающую строку, максимальную длину и суффикс, добавляемый при укорачивании. Это не потребует усложнения кода, и позволит обойтись без «магических констант».
Проверим:
Используем написанную функцию:
2. Укорачивание названия книги с переносом по словам
Проверим:
Page 21: Page 21
Page 22: Практика+
- Напишите PL/pgSQL-функцию, которая возвращает строку заданной длины из случайных символов. 2. Задача про игру в «наперстки». В одном из трех наперстков спрятан выигрыш. Игрок выбирает один из этих трех. Ведущий убирает один из двух оставшихся наперстков (обязательно пустой) и дает игроку возможность поменять решение, то есть выбрать второй из двух оставшихся. Есть ли смысл игроку менять выбор или нет смысла менять первоначальный вариант? Задание: используя PL/pgSQL, посчитайте вероятность выигрыша и для начального выбора, и для измененного.
Комментарии преподавателя
Предварительно можно создать функцию rnd_integer, которая возвращает случайное целое число в заданном диапазоне. Функция будет полезна для решения обоих заданий.
- Помимо длины строки на вход функции можно подавать список допустимых символов. По умолчанию, это могут быть все символы алфавита, числа и некоторые знаки. Для определения случайных символов из списка можно использовать функцию rnd_integer. Объявление функции может быть таким:
- Для решения можно использовать анонимный блок.
Сначала нужно реализовать одну игру и посмотреть, какой вариант выиграл: начальный или измененный. Для загадывания и угадывания одного из трех наперстков можно использовать rnd_integer(1,3).
Затем игру поместить в цикл и «сыграть», например, 1000 раз, подсчитывая, какой вариант сколько раз победил. В конце через RAISE NOTICE вывести значения счетчиков и выявить победивший вариант (или отсутствие такового).
Page 23: 1. Случайная строка заданного размера
Вначале определим вспомогательную функцию для получения случайного целого числа в заданном диапазоне. Такую функцию легко написать на чистом SQL, но здесь представлен вариант на PL/pgSQL:
Проверяем работу:
Функция гарантирует равномерное распределение случайных значений по всему диапазону, включая граничные значения:
Теперь можно приступить к написанию функции для получения случайной строки заданного размера. Будем использовать функцию rnd_integer для получения случайного символа из списка.
Проверяем:
Page 24: 2. Игра в наперстки
Для загадывания и угадывания наперстка используем rnd_integer(1,3).
Вначале мы выбираем 1 из 3, поэтому вероятность начального выбора 1/3. Если же выбор изменить, то изменится и вероятность на противоположные 2/3. Таким образом, вероятность выиграть при смене выбора выше. Поэтому есть смысл выбор поменять.