09 Занятие 09: SQL / Процедуры
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dev1_09_sql_proc.html Archive: DEV1-handouts-16.zip Страниц PDF: 19
Page 1: SQL / Процедуры
Авторские права
© Postgres Professional, 2017–2024 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Процедуры и их отличие от функций
- Входные и выходные параметры
- Перегрузка и полиморфизм
Page 3: Подпрограммы
Функции
- вызываются в контексте выражения
- не могут управлять транзакциями
- возвращают результат
- Процедуры
- вызываются оператором CALL
- могут управлять транзакциями
- могут возвращать результат
Комментарии преподавателя
Процедуры были введены в PostgreSQL 11. Основная причина их появления состоит в том, что функции не могут управлять транзакциями. Функции вызываются в контексте какого-либо выражения, которое вычисляется как часть уже начатого оператора (например, SELECT) в уже начатой транзакции. Нельзя завершить транзакцию и начать новую «посередине» выполнения оператора.
Процедуры всегда вызываются специальным оператором CALL. Если этот оператор сам начинает новую транзакцию (а не вызывается из уже начатой), то в процедуре можно использовать команды управления транзакциями.
К сожалению, процедуры, написанные на языке SQL, лишены сейчас возможности использовать команды COMMIT и ROLLBACK, хотя те из них, что оформлены в новом стиле стандарта SQL, вероятно смогут это делать в будущем. Поэтому пример процедуры, управляющей транзакциями, придется отложить до темы «PL/pgSQL. Выполнение запросов».
Иногда можно услышать, что процедура отличается от функции тем, что не возвращает результат. Но это не так — процедуры тоже могут возвращать результат, если необходимо.
Функции и процедуры имеют общее пространство имен и вместе называются подпрограммами (routine).
https://postgrespro.ru/docs/postgresql/16/sql-createprocedure
https://postgrespro.ru/docs/postgresql/16/sql-call
Page 4: Процедуры без параметров
Начнем с примера простой процедуры без параметров.
Чтобы вызвать процедуру, необходимо использовать специальный оператор:
Результат работы виден в таблице:
А теперь переопределим нашу процедуру в стиле стандарта SQL:
Убедимся в ее работоспособности:
И попробуем в процедуре выполнить фиксацию транзакции:
Обратите внимание, что мы получили ошибку о недопустимой команде еще на этапе определения подпрограммы. Переименуем таблицу, с которой работает наша процедура:
Page 5: Page 5
Вызов ниже не приведет к ошибке — таблица в определении процедуры в системном каталоге теперь представлена не по имени, а по идентификатору, который был получен еще на этапе создания подпрограммы.
Ту же самую задачу, что выполняла процедура, можно решить и с помощью функции, возвращаемое значение которой определяется последним оператором. Можно объявить тип результата void, если фактически функция ничего не возвращает, или вернуть что-то осмысленное. Возвратим таблице прежнее имя и определим функцию:
В любом случае функция вызывается в контексте какого-либо выражения:
Чего нельзя достичь с помощью функции — это управления транзакциями. Но и в процедурах на языке SQL, как мы видели, это пока не поддерживается (зато поддерживается при использовании других языков).
Процедуры с параметрами
Добавим в процедуру входной параметр — число строк:
Точно так же, как и в случае функций, при вызове процедур фактические параметры можно передавать позиционным способом или по имени:
Page 6: Page 6
Процедуры могут также иметь OUT-и INOUT-параметры, с помощью которых можно возвращать значения:
Попробуем:
Page 7: Перегрузка
Несколько подпрограмм с одним и тем же именем
- подпрограмма однозначно определяется сигнатурой —
- именем и входными параметрами
- тип возвращаемого значения и выходные параметры игнорируются
- подходящая подпрограмма выбирается во время выполнения
- в зависимости от фактических параметров
- Команда CREATE OR REPLACE
- при несовпадении типов входных параметров создаст новую
- перегруженную подпрограмму
- при совпадении — изменит существующую подпрограмму,
- но нельзя поменять тип подпрограммы, тип возвращаемого значения,
- типы OUT-параметров
Комментарии преподавателя
Перегрузка — это возможность использования одного и того же имени для нескольких подпрограмм, отличающихся типами параметров IN и INOUT.
Сигнатура подпрограммы — ее имя и типы входных параметров. При вызове PostgreSQL находит подпрограмму, соответствующую сигнатуре. Возможны ситуации, когда подходящую подпрограмму невозможно определить однозначно; в таком случае во время выполнения возникнет ошибка.
Однако в сигнатуру, в частности, не входят:
тип подпрограммы (процедура или функция);
типы параметров OUT;
тип возвращаемого значения.
Перегрузку надо учитывать, выполняя команду CREATE OR REPLACE (FUNCTION или PROCEDURE). Если сигнатура не совпадает ни с одной из существующих, будет создана новая перегруженная подпрограмма, а при совпадении — изменена существующая. В последнем случае не разрешается изменять тип подпрограммы, тип OUT-параметров или тип возвращаемого значения, однако можно изменить язык и другие свойства. Поэтому иногда приходится удалять подпрограмму и создавать ее заново, в этом случае потребуется также удалить зависящие от нее объекты: другие подпрограммы, представления, триггеры и т. п. (DROP ROUTINE … CASCADE).
https://postgrespro.ru/docs/postgresql/16/xfunc-overload
Page 8: Полиморфизм
Подпрограмма, принимающая параметры разных типов
- формальные параметры используют полиморфные псевдотипы
- (например, anyelement или anycompatible)
- конкретный тип данных выбирается во время выполнения
- по типу фактических параметров
Комментарии преподавателя
В некоторых случаях удобно не создавать несколько перегруженных подпрограмм для разных типов, а написать одну, принимающую параметры любого (или почти любого) типа.
Для этого в качестве типа формального параметра указывается специальный полиморфный псевдотип. Пока мы рассмотрим два из них — anyelement и anycompatible — но позже встретимся и с другими.
Полиморфные псевдотипы в определении позволяют использовать в качестве параметров любые типы, при этом конкретный тип, с которым будет работать подпрограмма, выбирается во время выполнения по типу фактического параметра.
Если в определении указано несколько полиморфных параметров типа anyelement, то все фактические параметры неявно приводятся к типу первого из них. Если же указано несколько параметров типа anycompatible, то типы фактических параметров приводятся к некоторому общему типу.
Если подпрограмма объявлена с возвращаемым значением полиморфного типа, то она должна иметь по крайней мере один входной полиморфный параметр. Конкретный тип возвращаемого значения также определяется исходя из типа фактического входного параметра. Для подпрограмм в стиле стандарта SQL возможности использовать полиморфные типы данных для аргументов нет.
https://postgrespro.ru/docs/postgresql/16/extend-type-system#EXTEND-TY PES-POLYMORPHIC
https://postgrespro.ru/docs/postgresql/16/xfunc-sql#XFUNC-SQL-POLYMO RPHIC-FUNCTIONS
Page 9: Перегруженные подпрограммы
Перегрузка работает одинаково и для функций, и для процедур. Они имеют общее пространство имен. В качестве примера напишем функцию, возвращающую большее из двух целых чисел. (Похожее выражение есть в SQL и называется greatest, но мы напишем собственную функцию.)
Проверим:
Допустим, мы решили сделать аналогичную функцию для трех чисел. Благодаря перегрузке, не надо придумывать для нее какое-то новое название:
Теперь у нас две функции с одним именем, но разным числом параметров:
И обе работают:
Команда CREATE OR REPLACE позволяет создать подпрограмму или заменить существующую, не удаляя ее. Поскольку в данном случае функция с такой сигнатурой уже существует, она будет заменена:
Пусть наша функция работает не только для целых чисел, но и для вещественных. Как этого добиться? Можно определить еще такую функцию:
Page 10: Page 10
Теперь у нас три функции с одинаковым именем:
Две из них имеют одинаковое количество параметров, но отличаются их типами:
Если подпрограмма перегружена несколько раз, то для получения информации только о некоторых из них можно указать в команде \df типы интересующих параметров:
Дальше нам придется определить функции для всех остальных типов данных и повторить все то же самое для трех параметров. Притом что операторы в теле этих функций будут одни и те же.
Полиморфные функции
Здесь нам помогут полиморфные типы anyelement и anycompatible. Это псевдотипы, взамен которых при вызове и интерпретации функции будет подставлен тип фактического параметра. Разумеется, в случае определения подпрограммы в стиле стандарта SQL ее код будет разобран еще на этапе создания, и воспользоваться полиморфными псевдотипами не удастся. Удалим все три наши функции…
…и затем создадим новую:
Такая функция должна принимать любой тип данных (а работать будет с любым типом, для которого определен оператор «больше»). Получится?
Увы, нет. В данном случае строковые литералы могут быть типа char, varchar, text — конкретный тип нам неизвестен. Но можно применить явное приведение типов:
Page 11: Page 11
Еще пример с другим типом:
Тип результата функции всегда будет тот же, что и тип параметров. Но можно продвинуться еще дальше — сделать так, чтобы можно было использовать в полиморфных подпрограммах не абсолютно одинаковые типы, а совместимые, то есть те, что могут быть приведены друг к другу неявно. Для этого нужно использовать полиморфный псевдотип anycompatible. Удалим нашу функцию и взамен нее создадим другую:
Повторим наш опыт с параметрами-литералами:
Получилось! Но если типы параметров не совпадают и не могут быть неявно приведены к некоторому общему типу, то будет ошибка:
В этом примере такое ограничение выглядит естественно, хотя в некоторых случаях оно может оказаться и неудобным. Определим теперь функцию с тремя параметрами, но так, чтобы третий можно было не указывать:
Page 12: Page 12
Так работает. А так?
А так произошел конфликт перегруженных функций:
Невозможно понять, имеем ли мы в виду функцию с двумя параметрами, или с тремя (но просто не указали последний). Мы решим этот конфликт просто — удалим первую функцию за ненадобностью.
Теперь все работает. А в теме «PL/pgSQL. Массивы» мы узнаем, как определять подпрограммы с произвольным числом параметров.
Page 13: Итоги
Можно создавать и использовать собственные процедуры
- В отличие от функций, процедуры вызываются оператором CALL и могут управлять транзакциями
- Для процедур и функций поддерживаются перегрузка и полиморфизм
Page 14: Практика
- В таблице authors имена, фамилии и отчества авторов по смыслу должны быть уникальны, но это условие никак не проверяется. Напишите процедуру, удаляющую возможные дубликаты авторов. 2. Чтобы необходимость в подобной процедуре не возникала, создайте ограничение целостности, которое не позволит появляться дубликатам в будущем.
Комментарии преподавателя
- В приложении возможность добавлять авторов появится в теме «PL/pgSQL. Выполнение запросов». А пока для проверки можно добавить дубликаты в таблицу вручную.
Page 15: 1. Устранение дубликатов
В целях проверки добавим второго Пушкина:
Задачу устранения дубликатов можно решить разными способами. Например, так:
2. Ограничение целостности
Создать подходящее ограничение целостности мешает тот факт, что отчество может быть неопределенным (NULL). Неопределенные значения считаются различными, поэтому ограничение
не помешает добавить второго Джонатана Свифта без отчества. Задачу можно решить, создав уникальный индекс:
Page 16: Page 16
Проверим:
Page 17: Практика+
- Получится ли создать в одной и той же схеме и имеющие одно и то же имя: а) процедуру с одним входным параметром, б) функцию с одним входным параметром того же типа, возвращающую некоторое значение? в) процедуру с одним входным и одним выходным параметром? Проверьте. 2. В таблице хранятся вещественные числа (например, результаты каких-либо измерений). Напишите процедуру нормализации данных, которая умножает все числа на определенный коэффициент так, чтобы все значения попали в интервал от −1 до 1. Процедура должна возвращать выбранный коэффициент.
Комментарии преподавателя
- В качестве коэффициента возьмите максимальное абсолютное значение из таблицы.
Page 18: 1. Перегрузка процедур и функций
Не получится, так как в сигнатуру подпрограммы входит только имя и тип входных параметров (возвращаемое значение игнорируется), и при этом процедуры и функции имеют общее пространство имен.
В некоторых сообщениях, как и в этом, вместо слова «процедура» используется «функция», поскольку во многом они устроены одинаково.
Такую процедуру тоже создать нельзя, так как уже имеется процедура с такой же сигнатурой, а изменять выходные параметры (и факт их наличия) для имеющейся подпрограммы также запрещено. Нам предлагают удалить подпрограмму, чтобы затем создать ее заново.
2. Нормализация данных
Таблица с тестовыми данными:
Процедуру можно написать, используя один SQL-оператор: