40.7. Курсоры#

40.7. Курсоры

40.7. Курсоры

Вместо выполнения всего запроса сразу, можно настроить курсор, который инкапсулирует запрос, а затем читать результат запроса по несколько строк за раз. Одна из причин для этого - избежать переполнения памяти, когда результат содержит большое количество строк. (Однако пользователи PL/pgSQL обычно не должны беспокоиться об этом, поскольку циклы FOR автоматически используют курсор внутри для избежания проблем с памятью). Более интересное использование - возвращение ссылки на курсор, который создала функция, позволяя вызывающей стороне читать строки. Это обеспечивает эффективный способ возвращать большие наборы строк из функций.

40.7.1. Объявление переменных курсора

Вся работа с курсорами в PL/pgSQL осуществляется через переменные-курсоры, которые всегда имеют специальный тип данных refcursor. Один из способов создания переменной-курсора - это просто объявить ее как переменную типа refcursor. Другой способ - использовать синтаксис объявления курсора, который в общем виде выглядит так:

name [[ NO ] SCROLL] CURSOR [ ( arguments ) ] FOR query;

(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

OPEN unbound_cursorvar [[ NO ] SCROLL] FOR query;

Переменная курсора открывается и получает указанный запрос для выполнения. Курсор не может быть уже открыт, и он должен быть объявлен как непривязанная переменная курсора (то есть как простая переменная 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

OPEN unbound_cursorvar [[ NO ] SCROLL] FOR EXECUTE query_string
                                     [USING expression [, ... ]];

Переменная курсора открывается и получает указанный запрос для выполнения. Курсор не может быть уже открыт, и он должен быть объявлен как независимая переменная курсора (то есть как простая переменная 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. Открытие привязанного курсора

OPEN bound_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 INTO target;

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

UPDATE table SET ... WHERE CURRENT OF cursor;
DELETE FROM table WHERE CURRENT OF cursor;

Когда курсор находится на строке таблицы, эту строку можно обновить или удалить, используя курсор для идентификации строки. Существуют ограничения на запрос курсора (в частности, отсутствие группировки), и лучше использовать 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>> ]
FOR recordvar IN bound_cursorvar [( [ argument_name := ] argument_value [, ...] )] LOOP
    statements
END LOOP [ label ];

Переменная курсора должна быть привязана к какому-то запросу при ее объявлении, и она не может быть уже открыта. Оператор FOR автоматически открывает курсор и закрывает его снова при выходе из цикла. Список фактических значений аргументов должен появиться только в том случае, если курсор был объявлен с аргументами. Эти значения будут подставлены в запрос таким же образом, как и при выполнении оператора OPEN (см. раздел Раздел 40.7.2.3).

Переменная recordvar автоматически определена как тип record и существует только внутри цикла (любое существующее определение имени переменной игнорируется внутри цикла). Каждая строка, возвращаемая курсором, последовательно присваивается этой переменной-записи, и выполняется тело цикла.