40.7. Курсоры#
40.7. Курсоры
Вместо выполнения всего запроса сразу, можно настроить курсор, который инкапсулирует запрос, а затем читать результат запроса по несколько строк за раз. Одна из причин для этого - избежать переполнения памяти, когда результат содержит большое количество строк. (Однако пользователи PL/pgSQL обычно не должны беспокоиться об этом, поскольку циклы FOR
автоматически используют курсор внутри для избежания проблем с памятью). Более интересное использование - возвращение ссылки на курсор, который создала функция, позволяя вызывающей стороне читать строки. Это обеспечивает эффективный способ возвращать большие наборы строк из функций.
40.7.1. Объявление переменных курсора
Вся работа с курсорами в PL/pgSQL осуществляется через переменные-курсоры, которые всегда имеют специальный тип данных refcursor
. Один из способов создания переменной-курсора - это просто объявить ее как переменную типа refcursor
. Другой способ - использовать синтаксис объявления курсора, который в общем виде выглядит так:
name
[[ NO ] SCROLL] CURSOR [ (arguments
) ] FORquery
;
(FOR
может быть заменено на IS
для
обеспечения совместимости с Oracle).
Если указано SCROLL
, курсор будет способен
перемещаться назад; если указано NO SCROLL
, запросы
на перемещение назад будут отклонены; если ни одна из этих
спецификаций не указана, то зависит от запроса, будет ли
разрешено перемещение назад.
arguments
, если указано, является
списком пар
, которые определяют имена,
которые будут заменены значениями параметров в данном запросе. Фактические
значения для замены этих имен будут указаны позже,
при открытии курсора.
name
datatype
Некоторые примеры:
DECLARE curs1 refcursor; curs2 CURSOR FOR SELECT * FROM tenk1; curs3 CURSOR (key integer) FOR SELECT * FROM tenk1 WHERE unique1 = key;
Все три эти переменные имеют тип данных refcursor
,
но первая может использоваться с любым запросом, в то время как вторая
уже имеет полностью определенный запрос, привязанный к ней, а последняя
имеет параметризованный запрос, привязанный к ней. (key
будет
заменено значением целочисленного параметра при открытии курсора).
Переменная curs1
считается непривязанной, так как она не привязана к
какому-либо конкретному запросу.
Вариант SCROLL
не может быть использован, когда запрос курсора использует FOR UPDATE/SHARE
. Также лучше использовать NO SCROLL
с запросом, включающим волатильные функции. Реализация SCROLL
предполагает, что повторное чтение вывода запроса даст последовательные результаты, что волатильная функция может не сделать.
40.7.2. Открытие курсоров
Перед тем, как курсор может быть использован для извлечения строк, его необходимо открыть. (Это эквивалентное действие команде SQL DECLARE CURSOR
). PL/pgSQL имеет три формы оператора OPEN
, две из которых используют несвязанные переменные курсора, а третья использует связанную переменную курсора.
Примечание
Все переменные привязанных курсоров также могут использоваться без явного открытия курсора с помощью оператора FOR
, описанного в разделе Раздел 40.7.4.
40.7.2.1. OPEN FOR
query
OPENunbound_cursorvar
[[ NO ] SCROLL] FORquery
;
Переменная курсора открывается и получает указанный запрос для выполнения. Курсор не может быть уже открыт, и он должен быть объявлен как непривязанная переменная курсора (то есть как простая переменная refcursor
). Запрос должен быть SELECT
или что-то еще, что возвращает строки (например, EXPLAIN
). Запрос обрабатывается так же, как и другие SQL-команды в PL/pgSQL: имена переменных PL/pgSQL заменяются, и план запроса кешируется для возможного повторного использования. Когда переменная PL/pgSQL подставляется в запрос курсора, значение, которое подставляется, - это значение, которое оно имеет на момент OPEN
; последующие изменения переменной не повлияют на поведение курсора. Опции SCROLL
и NO SCROLL
имеют те же значения, что и для привязанного курсора.
Пример:
OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;
40.7.2.2. OPEN FOR EXECUTE
OPENunbound_cursorvar
[[ NO ] SCROLL] FOR EXECUTEquery_string
[USINGexpression
[, ... ]];
Переменная курсора открывается и получает указанный запрос для выполнения. Курсор не может быть уже открыт, и он должен быть объявлен как независимая переменная курсора (то есть как простая переменная refcursor
). Запрос указывается как строковое выражение, так же, как в команде EXECUTE
. Как обычно, это обеспечивает гибкость, чтобы план запроса мог изменяться от одного запуска к другому (см. Раздел 40.11.2), и также означает, что подстановка переменных не выполняется в строке команды. Как и в случае с EXECUTE
, значения параметров могут быть вставлены в динамическую команду с помощью format()
и USING
. Опции SCROLL
и NO SCROLL
имеют те же значения, что и для привязанного курсора.
Пример:
OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;
В этом примере имя таблицы вставляется в запрос с помощью функции format()
. Значение для сравнения с col1
вставляется с помощью параметра USING
, поэтому оно не требует кавычек.
40.7.2.3. Открытие привязанного курсора
OPENbound_cursorvar
[( [argument_name
:= ]argument_value
[, ...] )];
Эта форма команды OPEN
используется для открытия курсора, переменная которого была связана с запросом при ее объявлении. Курсор не может быть уже открыт. Список фактических выражений значений аргументов должен появиться только в том случае, если курсор был объявлен с аргументами. Эти значения будут подставлены в запрос.
Всегда считается, что план запроса для привязанного курсора может быть кеширован; в этом случае нет эквивалента команды EXECUTE
. Обратите внимание, что SCROLL
и NO SCROLL
нельзя указать в OPEN
, так как поведение курсора уже было определено.
Все значения аргументов могут быть переданы с использованием либо позиционной нотации, либо именованной передачи аргументов. В позиционной записи все аргументы указываются в порядке. В именованной записи имя каждого аргумента указывается с использованием :=
для разделения его от выражения аргумента. Аналогично вызову функций, описанному в Раздел 4.3, также допускается смешивание позиционной и именованной записи.
Примеры (эти примеры используют примеры объявления курсора выше):
OPEN curs2; OPEN curs3(42); OPEN curs3(key := 42);
Поскольку подстановка переменных выполняется в запросе привязанного курсора, существует два способа передачи значений в курсор: либо с явным аргументом для OPEN
, либо неявно, ссылаясь на переменную PL/pgSQL в запросе. Однако, только переменные, объявленные до объявления привязанного курсора, будут подставлены в него. В любом случае, значение, которое будет передано, определяется в момент выполнения OPEN
. Например, другой способ достичь того же результата, что и в примере curs3
выше, это
DECLARE key integer; curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key; BEGIN key := 42; OPEN curs4;
40.7.3. Использование курсоров
После открытия курсора его можно изменять с помощью описанных здесь операторов.
Эти манипуляции не обязательно должны происходить в той же функции, которая открыла курсор. Вы можете вернуть значение refcursor
из функции и позволить вызывающей стороне работать с курсором. (Внутренне значение refcursor
представляет собой просто строковое имя так называемого портала, содержащего активный запрос для курсора. Это имя может передаваться, присваиваться другим переменным типа refcursor
и т. д., не нарушая портал).
Все порталы автоматически закрываются при завершении транзакции. Поэтому значение refcursor
можно использовать для ссылки на открытый курсор только до конца транзакции.
40.7.3.1. FETCH
FETCH [direction
{ FROM | IN } ]cursor
INTOtarget
;
FETCH
извлекает следующую строку (в указанном
направлении) из
курсора в целевую переменную, которая может быть переменной строки, переменной записи или списком простых переменных, разделенных запятыми, так же как
SELECT INTO
. Если подходящей строки нет,
целевая переменная устанавливается в NULL. Как и в случае с SELECT
INTO
, специальную переменную FOUND
можно
проверить, чтобы узнать, была ли получена строка или нет. Если строка не получена, курсор позиционируется после последней строки или перед первой строкой, в зависимости от направления движения.
Предложение direction
может быть любым из вариантов, разрешенных в команде SQL FETCH, за исключением тех, которые могут выбирать более одной строки; а именно, это может быть NEXT
, PRIOR
, FIRST
, LAST
, ABSOLUTE
count
, RELATIVE
count
, FORWARD
или BACKWARD
. Отсутствие direction
эквивалентно указанию NEXT
. В формах, использующих count
, count
может быть любым выражением с целочисленным значением (в отличие от команды SQL FETCH
, которая позволяет только целочисленную константу). Значения direction
, которые требуют перемещения назад, скорее всего, не будут работать, если курсор был объявлен или открыт с опцией SCROLL
.
cursor
должен быть именем переменной refcursor
,
которая ссылается на открытый портал курсора.
Примеры:
FETCH curs1 INTO rowvar; FETCH curs2 INTO foo, bar, baz; FETCH LAST FROM curs3 INTO x, y; FETCH RELATIVE -2 FROM curs4 INTO x;
40.7.3.2. MOVE
MOVE [direction
{ FROM | IN } ]cursor
;
MOVE
перемещает курсор без извлечения
каких-либо данных. MOVE
работает как
команда FETCH
, за исключением того, что она только перемещает
курсор и не возвращает строку, к которой переместился.
Предложение direction
может быть любым из
вариантов, разрешенных в команде SQL FETCH,
включая те, которые могут извлекать более одной строки;
курсор позиционируется на последнюю такую строку.
(Однако случай, когда предложение direction
является просто выражением count
без
ключевого слова, устарел в PL/pgSQL.
Этот синтаксис неоднозначен в случае, когда
предложение direction
полностью опущена,
и поэтому он может не сработать, если
count
не является константой.)
Как и в случае с SELECT
INTO
, специальную переменную FOUND
можно
проверить, чтобы узнать, была ли строка для перемещения. Если такой
строки нет, курсор позиционируется после последней строки или перед
первой строкой, в зависимости от направления перемещения.
Примеры:
MOVE curs1; MOVE LAST FROM curs3; MOVE RELATIVE -2 FROM curs4; MOVE FORWARD 2 FROM curs4;
40.7.3.3. UPDATE/DELETE WHERE CURRENT OF
UPDATEtable
SET ... WHERE CURRENT OFcursor
; DELETE FROMtable
WHERE CURRENT OFcursor
;
Когда курсор находится на строке таблицы, эту строку можно обновить или удалить, используя курсор для идентификации строки. Существуют ограничения на запрос курсора (в частности, отсутствие группировки), и лучше использовать FOR UPDATE
в курсоре. Дополнительную информацию см. на странице справки DECLARE.
Пример:
UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;
40.7.3.4. CLOSE
CLOSE cursor
;
CLOSE
закрывает портал, лежащий в основе открытого курсора. Это может быть использовано для освобождения ресурсов раньше, чем в конце транзакции, или для освобождения переменной курсора для повторного открытия.
Пример:
CLOSE curs1;
40.7.3.5. Возвращение курсоров
PL/pgSQL функции могут возвращать курсоры вызывающей стороне. Это полезно для возврата нескольких строк или столбцов, особенно с очень большими наборами результатов. Для этого функция открывает курсор и возвращает имя курсора вызывающей стороне (или просто открывает курсор, используя имя портала, указанное или известное вызывающей стороне). Затем вызывающая сторона может извлекать строки из курсора. Курсор может быть закрыт вызывающей стороной, или он будет закрыт автоматически при закрытии транзакции.
Возможное имя портала, используемое для курсора, может быть указано программистом или сгенерировано автоматически. Чтобы указать имя портала, просто присвойте строку переменной refcursor
перед его открытием. Значение строки переменной refcursor
будет использоваться командой OPEN
в качестве имени базового портала. Однако, если переменная refcursor
равна null, OPEN
автоматически генерирует имя, которое не конфликтует с существующими порталами, и присваивает его переменной refcursor
.
Примечание
Переменная привязанного курсора инициализируется строковым значением, представляющим его имя, так что имя портала совпадает с именем переменной курсора, если программист не переопределяет его присваиванием перед открытием курсора. Но переменная непривязанного курсора по умолчанию имеет значение null, поэтому она получит автоматически сгенерированное уникальное имя, если не переопределена.
Следующий пример показывает один из способов, которым имя курсора может быть предоставлено вызывающей стороной:
CREATE TABLE test (col text); INSERT INTO test VALUES ('123'); CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS ' BEGIN OPEN $1 FOR SELECT col FROM test; RETURN $1; END; ' LANGUAGE plpgsql; BEGIN; SELECT reffunc('funccursor'); FETCH ALL IN funccursor; COMMIT;
Следующий пример использует автоматическую генерацию имени курсора:
CREATE FUNCTION reffunc2() RETURNS refcursor AS ' DECLARE ref refcursor; BEGIN OPEN ref FOR SELECT col FROM test; RETURN ref; END; ' LANGUAGE plpgsql; -- need to be in a transaction to use cursors. BEGIN; SELECT reffunc2(); reffunc2 -------------------- <unnamed cursor 1> (1 row) FETCH ALL IN "<unnamed cursor 1>"; COMMIT;
Следующий пример показывает один из способов возвращения нескольких курсоров из одной функции:
CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$ BEGIN OPEN $1 FOR SELECT * FROM table_1; RETURN NEXT $1; OPEN $2 FOR SELECT * FROM table_2; RETURN NEXT $2; END; $$ LANGUAGE plpgsql; -- need to be in a transaction to use cursors. BEGIN; SELECT * FROM myfunc('a', 'b'); FETCH ALL FROM a; FETCH ALL FROM b; COMMIT;
40.7.4. Циклический проход по результатам курсора
Существует вариант оператора FOR
, который позволяет итерироваться по строкам, возвращаемым курсором. Синтаксис следующий:
[ <<label
>> ] FORrecordvar
INbound_cursorvar
[( [argument_name
:= ]argument_value
[, ...] )] LOOPstatements
END LOOP [label
];
Переменная курсора должна быть привязана к какому-то запросу при ее объявлении, и она не может быть уже открыта. Оператор FOR
автоматически открывает курсор и закрывает его снова при выходе из цикла. Список фактических значений аргументов должен появиться только в том случае, если курсор был объявлен с аргументами. Эти значения будут подставлены в запрос таким же образом, как и при выполнении оператора OPEN
(см. раздел Раздел 40.7.2.3).
Переменная recordvar
автоматически
определена как тип record
и существует только внутри цикла (любое
существующее определение имени переменной игнорируется внутри цикла).
Каждая строка, возвращаемая курсором, последовательно присваивается этой
переменной-записи, и выполняется тело цикла.