13 Занятие 13: PL/pgSQL / Курсоры
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dev1_13_plpgsql_cursors.html Archive: DEV1-handouts-16.zip Страниц PDF: 19
Page 1: PL/pgSQL / Курсоры
Авторские права
© Postgres Professional, 2017–2024 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Причины использования
- Объявление и открытие курсора
- Операции с курсором
- Циклы по курсору и по результатам запроса
- Передача курсора клиенту
Page 3: Причины использования
Курсор подразумевает итеративную обработку
- полная выборка занимает слишком много памяти
- нужна не вся выборка, но размер заранее неизвестен
- способ отдать управление выборкой клиенту
- действительно требуется построчная обработка (обычно нет)
Комментарии преподавателя
С концепцией курсоров мы уже знакомы по теме «Архитектура. Общее устройство PostgreSQL». Там речь шла о курсорах как о возможности, предоставляемой сервером, и мы смотрели, как к этой возможности обращаться средствами SQL. Теперь поговорим о том, как использовать те же самые курсоры в языке PL/pgSQL.
Зачем вообще может возникнуть необходимость в курсорах? Декларативный SQL в первую очередь предназначен для работы с множествами строк — в этом его сила и преимущество. PL/pgSQL, как процедурный язык, должен работать со строками по одной за раз, используя явные циклы. Такой подход как раз реализуется при помощи курсоров.
Например, полная выборка может занимать слишком много места, так что приходится обрабатывать результаты по частям. Или мы получаем набор данных неизвестного заранее размера — и в процессе выборки нужно вовремя остановиться. Или есть необходимость предоставить управление выборкой клиенту.
(Однако еще раз отметим, что хотя необходимость такой построчной обработки может возникать, в большом количестве случаев ее можно заменить чистым SQL; код в итоге окажется проще и будет быстрее работать.)
Page 4: Объявление и открытие
Не связанные с запросом курсорные переменные
- объявляется переменная типа refcursor
- конкретный запрос указывается при открытии
- нет в SQL
- Связанные с запросом курсорные переменные
- при объявлении указывается запрос (возможно, с параметрами)
- при открытии указываются фактические значения параметров
- Особенности
- значение курсорной переменной — имя курсора
- (можно задать явно или сгенерируется автоматически)
- переменные PL/pgSQL в запросе становятся неявными параметрами
- (значения подставляются при открытии курсора)
- запрос предварительно подготавливается
Комментарии преподавателя
Как мы видели, в SQL курсор объявлялся и открывался одновременно командой DECLARE. В PL/pgSQL это два отдельных шага. Кроме того, для доступа к курсорам используются так называемые курсорные переменные, имеющие тип refcursor и, фактически, содержащие имя курсора (причем если не указывать это имя явно, PL/pgSQL сам позаботится о его уникальности).
Курсорную переменную можно объявить, не связывая ее с конкретным запросом. Тогда при открытии курсора нужно будет указать запрос.
Другой вариант — уже при объявлении переменной указать запрос, возможно, с параметрами. Тогда при открытии курсора указываются только фактические параметры.
Оба способа равноценны; какой использовать — дело вкуса. И связанные, и несвязанные курсорные переменные инициализируются только при открытии курсора; в обоих случаях соответствующий запрос может иметь неявные параметры — переменные PL/pgSQL.
Напомним, что запрос, открытый с помощью курсора, автоматически подготавливается.
https://postgrespro.ru/docs/postgresql/16/plpgsql-cursors#PLPGSQL-CURS OR-DECLARATIONS
https://postgrespro.ru/docs/postgresql/16/plpgsql-cursors#PLPGSQL-CURS OR-OPENING
Page 5: Объявление и открытие
Создадим таблицу:
Несвязанная переменная:
Связанная переменная: запрос указывается уже при объявлении. При этом переменная cur имеет тот же тип refcursor.
В случае связанной переменной запрос может иметь параметры. Обратите внимание на устранение неоднозначности имен в этом и следующем примерах.
Переменные PL/pgSQL также являются (неявными) параметрами курсора.
В качестве запроса можно использовать не только команду SELECT, но и любую другую, возвращающую результат (например, INSERT, UPDATE, DELETE с фразой RETURNING).
Page 6: Операции с курсором
- в SQL
- размер выборки
- Выборка
- определяется
- только по одной строке
- Обращение к текущей строке курсора
- только для простых запросов
- (одна таблица, без группировок и сортировок)
- Обычно обработка выполняется в цикле
- цикл FOR по курсору
- цикл FOR по запросу без явного определения курсора
- в SQL
- DECLARE
- WITH HOLD
- Закрытие
- явно или автоматически при завершении транзакции
Комментарии преподавателя
Выборка строк из курсора возможна в PL/pgSQL только по одной. Для этого служит команда FETCH INTO.
Если запрос достаточно простой (одна таблица, без группировок и сортировок), то в процессе работы с курсором можно обращаться к текущей строке, например, в командах UPDATE или DELETE.
Процедурная обработка данных подразумевает работу с циклами. Можно организовать перебор и обработку всех строк, возвращаемых курсором, с помощью тех управляющих команд, что мы уже знаем. Но, поскольку часто нужен именно такой цикл, в PL/pgSQL есть специальная команда FOR. Целочисленный вариант FOR мы уже видели в теме «Обзор и конструкции языка», а этот вариант работает с курсором. Более того, есть еще один вариант цикла FOR, в котором не требуется даже объявления курсора — в команде указывается сам запрос.
Курсор можно закрыть явно командой CLOSE, но он в любом случае будет закрыт по окончании транзакции (в SQL курсор может оставаться открытым и после завершения транзакции, если указать фразу WITH HOLD).
https://postgrespro.ru/docs/postgresql/16/plpgsql-cursors#PLPGSQL-CURS OR-USING
https://postgrespro.ru/docs/postgresql/16/plpgsql-cursors#PLPGSQL-CURS OR-FOR-LOOP
Page 7: Операции с курсором
Команда FETCH читает следующую строку из курсора. Если нужно только переместиться на следующую строку, можно воспользоваться другой командой — MOVE. Что будет выведено на экран?
Обычно выборка происходит в цикле, который можно организовать так:
Но чтобы не писать много команд, в PL/pgSQL имеется цикл FOR по курсору, который делает ровно то же самое:
Более того, можно вообще обойтись без явной работы с курсором, если цикл — это все, что требуется. Скобки вокруг запроса не обязательны, но удобны.
Page 8: Page 8
Как и для любого цикла, здесь можно указать метку, что может оказаться полезным во вложенных циклах: Что будет выведено?
После выполнения цикла переменная FOUND позволяет узнать, была ли обработана хотя бы одна строка:
На текущую строку курсора, связанного с простым запросом (по одной таблице, без группировок и сортировок) можно сослаться с помощью предложения CURRENT OF. Типичный случай применения — обработка пакета заданий с изменением статуса каждого из них.
Заметим, что CURRENT OF не работает с циклом FOR по запросу, поскольку этот цикл не использует курсор явным образом. Конечно, аналогичный результат можно получить, явно указав в команде UPDATE или DELETE уникальный ключ таблицы (WHERE id = rec.id). Но CURRENT OF работает быстрее и не требует наличия индекса. Следует заметить, что в большом числе случаев вместо использования циклов можно выполнить задачу одним
Page 9: Page 9
оператором SQL — и это будет проще и еще быстрее. Часто циклы используют просто потому, что это более привычный, «процедурный» стиль программирования. Но для баз данных этот стиль не подходит. Например:
Такой цикл заменяется одной простой SQL-командой:
Page 10: Передача курсора клиенту
{#source-figure-010 caption=“Source slide page 10: Передача курсора клиенту”}
Комментарии преподавателя
Как уже говорилось, курсорная переменная PL/pgSQL (типа refcursor) содержит имя открытого SQL-курсора. Когда говорят о курсоре как о памяти, отведенной в обслуживающем процессе для хранения его состояния, в документации используется термин портал.
Таким образом, функция на PL/pgSQL может открыть курсор и вернуть его имя клиенту. Дальше клиент сможет работать с курсором так, как будто он сам его и открыл, но доступ будет иметь только к тем данным, которые предоставлены при открытии. Это дает еще одну возможность для организации взаимодействия приложения и базы данных.
Page 11: Передача курсора клиенту
Откроем курсор и выведем значение курсорной переменной:
Это имя курсора (портала), который был открыт на сервере. Оно было сгенерировано автоматически. При желании имя можно задать явно (но оно должно быть уникальным в сеансе):
Пользуясь этим, можно написать функцию, которая откроет курсор и вернет его имя:
Клиент начинает транзакцию…
…вызывает функцию, узнает имя курсора…
…и получает возможность читать из него данные (кавычки нужны из-за спецсимволов в имени):
Для удобства можно позволить клиенту самому устанавливать имя курсора:
Page 12: Page 12
Клиентский код упрощается:
Функция может вернуть и несколько открытых курсоров, используя OUT-параметры. Таким образом можно за один вызов функции обеспечить клиента информацией из разных таблиц, если это необходимо. Альтернативный подход — сразу выбрать все необходимые данные на стороне сервера и сформировать из них документ JSON или XML. Работа с этими форматами рассматривается в курсе DEV2.
Page 13: Итоги
Курсор позволяет получать и обрабатывать данные построчно
- Цикл FOR упрощает работу с курсорами
- Обработка в цикле естественна для процедурных языков, но этим не стоит злоупотреблять
Page 14: Практика
- Измените функцию book_name: если у книги больше двух авторов, то в названии должны указываться первые два с добавлением «и др.» в конце.
- Проверьте работу функции в SQL и в приложении. 2. Попробуйте написать функцию book_name на SQL. Какой вариант нравится больше — PL/pgSQL или SQL?
- Например:
Page 15: 1. Функция book_name (сокращение авторов)
Напишем более универсальную функцию с дополнительным параметром — максимальное число авторов в названии. Поскольку функция меняет сигнатуру (число и/или типы входных параметров), ее необходимо сначала удалить, а потом создать заново. В данном случае у функции есть зависимый объект — представление catalog_v, в котором она используется. Представление тоже придется пересоздать (в реальной работе все эти действия надо выполнять в одной транзакции, чтобы изменения вступили в силу атомарно).
Не забудем также создать заново удаленную выше функцию get_catalog:
Page 16: Page 16
2. Вариант на чистом SQL
Page 17: Практика+
- Требуется распределить расходы на электроэнергию по отделам компании пропорционально количеству сотрудников (перечень отделов находится в таблице). Напишите функцию, которая примет общую сумму расходов и запишет распределенные расходы в строки таблицы. Числа округляются до копеек; сумма расходов всех отделов должна в точности совпадать с общей суммой. 2. Напишите табличную функцию, имитирующую сортировку слиянием. Функция принимает две курсорные переменные; оба курсора уже открыты и возвращают упорядоченные по неубыванию целые числа. Требуется выдать общую упорядоченную последовательность чисел из обоих источников.
Комментарии преподавателя
- В качестве таблицы можно взять:
Функция:
Ожидаемый результат после вызова функции с параметром 100.0:
- Функция:
Например, если первый курсор возвращает последовательность 1, 3, 5, а второй — 2, 3, 4, то ожидается результат:
Page 18: 1. Распределение расходов
Таблица:
Функция:
Проверка:
Разумеется, возможны и другие алгоритмы, например, перенос всех ошибок округления на одну строку и т. п. В курсе DEV2 рассматривается другое решение этой задачи с помощью пользовательских агрегатных функций.
2. Слияние отсортированных наборов
Page 19: Page 19
Эта реализация предполагает, что числа не могут иметь неопределенные значения NULL.
Проверяем.