41.7. Cursores #

41.7.1. Declaración de variables de cursor
41.7.2. Apertura de cursores
41.7.3. Uso de cursores
41.7.4. Bucle a través del resultado de un cursor

En lugar de ejecutar una consulta completa a la vez, es posible configurar un cursor que encapsule la consulta y luego leer el resultado de la consulta unas pocas filas a la vez. Una razón para hacer esto es evitar el desbordamiento de memoria cuando el resultado contiene una gran cantidad de filas. (Sin embargo, los usuarios de PL/pgSQL no suelen necesitar preocuparse por eso, ya que los bucles FOR utilizan automáticamente un cursor de forma interna para evitar problemas de memoria). Un uso más interesante es devolver una referencia a un cursor que una función ha creado, permitiendo al llamador leer las filas. Esto proporciona una forma eficiente de devolver grandes conjuntos de filas desde funciones.

41.7.1. Declaración de variables de cursor #

Todo el acceso a los cursores en PL/pgSQL se realiza a través de variables de cursor, que son siempre del tipo de datos especial refcursor. Una forma de crear una variable de cursor es simplemente declararla como una variable de tipo refcursor. Otra forma es utilizar la sintaxis de declaración de cursor, que en general es:

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

(FOR se puede reemplazar por IS para compatibilidad con Oracle). Si se especifica SCROLL, el cursor podrá desplazarse hacia atrás; si se especifica NO SCROLL, se rechazarán las lecturas hacia atrás; si no aparece ninguna especificación, dependerá de la consulta si se permiten las lecturas hacia atrás. Los arguments, si se especifican, son una lista separada por comas de pares name datatype que definen los nombres que se reemplazarán por los valores de los parámetros en la consulta dada. Los valores reales a sustituir por estos nombres se especificarán más adelante, cuando se abra el cursor.

Algunos ejemplos:

DECLARE
    curs1 refcursor;
    curs2 CURSOR FOR SELECT * FROM tenk1;
    curs3 CURSOR (key integer) FOR SELECT * FROM tenk1 WHERE unique1 = key;

Las tres variables tienen el tipo de datos refcursor, pero la primera se puede usar con cualquier consulta, mientras que la segunda ya tiene una consulta completamente especificada vinculada (bound) a ella, y la última tiene una consulta parametrizada vinculada a ella. (key se reemplazará por un valor de parámetro entero cuando se abra el cursor). Se dice que la variable curs1 está desvinculada (unbound) ya que no está vinculada a ninguna consulta en particular.

La opción SCROLL no se puede utilizar cuando la consulta del cursor utiliza FOR UPDATE/SHARE. Además, es mejor usar NO SCROLL con una consulta que involucre funciones volátiles. La implementación de SCROLL asume que volver a leer la salida de la consulta dará resultados consistentes, lo cual podría no ocurrir con una función volátil.

41.7.2. Apertura de cursores #

Antes de poder utilizar un cursor para recuperar filas, este debe ser abierto. (Esta es la acción equivalente al comando SQL DECLARE CURSOR). PL/pgSQL tiene tres formas de la instrucción OPEN, dos de las cuales utilizan variables de cursor desvinculadas, mientras que la tercera utiliza una variable de cursor vinculada.

Note

Las variables de cursor vinculadas también se pueden utilizar sin abrir explícitamente el cursor, a través de la instrucción FOR descrita en Section 41.7.4. Un bucle FOR abrirá el cursor y luego lo cerrará nuevamente cuando se complete el bucle.

Abrir un cursor implica crear una estructura de datos interna del servidor llamada portal, que contiene el estado de ejecución de la consulta del cursor. Un portal tiene un nombre, que debe ser único dentro de la sesión durante la existencia del portal. Por defecto, PL/pgSQL asignará un nombre único a cada portal que cree. Sin embargo, si asignas un valor de cadena no nulo a una variable de cursor, esa cadena se utilizará como su nombre de portal. Esta característica se puede utilizar como se describe en Section 41.7.3.5.

41.7.2.1. OPEN FOR query #

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR query;

La variable de cursor se abre y se le proporciona la consulta especificada para ejecutar. El cursor no puede estar ya abierto y debe haber sido declarado como una variable de cursor desvinculada (es decir, como una simple variable refcursor). La consulta debe ser un SELECT o alguna otra cosa que devuelva filas (como EXPLAIN). La consulta se trata de la misma manera que otros comandos SQL en PL/pgSQL: se sustituyen los nombres de las variables de PL/pgSQL y el plan de consulta se almacena en caché para su posible reutilización. Cuando una variable de PL/pgSQL se sustituye en la consulta del cursor, el valor que se sustituye es el que tiene en el momento del OPEN; los cambios posteriores en la variable no afectarán al comportamiento del cursor. Las opciones SCROLL y NO SCROLL tienen los mismos significados que para un cursor vinculado.

Un ejemplo:

OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;

41.7.2.2. OPEN FOR EXECUTE #

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

La variable de cursor se abre y se le proporciona la consulta especificada para ejecutar. El cursor no puede estar ya abierto y debe haber sido declarado como una variable de cursor desvinculada (es decir, como una simple variable refcursor). La consulta se especifica como una expresión de cadena, de la misma manera que en el comando EXECUTE. Como es habitual, esto proporciona flexibilidad para que el plan de consulta pueda variar de una ejecución a otra (consulta la Section 41.11.2), y también significa que no se realiza la sustitución de variables en la cadena de comando. Al igual que con EXECUTE, los valores de los parámetros se pueden insertar en el comando dinámico a través de format() y USING. Las opciones SCROLL y NO SCROLL tienen los mismos significados que para un cursor vinculado.

Un ejemplo:

OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

En este ejemplo, el nombre de la tabla se inserta en la consulta a través de format(). El valor de comparación para col1 se inserta mediante un parámetro USING, por lo que no necesita comillas.

41.7.2.3. Apertura de un cursor vinculado #

OPEN bound_cursorvar [ ( [ argument_name { := | => } ] argument_value [, ...] ) ];

Esta forma de OPEN se utiliza para abrir una variable de cursor cuya consulta se vinculó a ella cuando se declaró. El cursor no puede estar ya abierto. Debe aparecer una lista de expresiones de valores de argumentos reales si y solo si el cursor fue declarado para recibir argumentos. Estos valores se sustituirán en la consulta.

El plan de consulta para un cursor vinculado siempre se considera almacenable en caché; no hay equivalente de EXECUTE en este caso. Ten en cuenta que SCROLL y NO SCROLL no se pueden especificar en OPEN, ya que el comportamiento de desplazamiento del cursor ya estaba determinado.

Los valores de los argumentos se pueden pasar utilizando notación posicional o nominal. En la notación posicional, todos los argumentos se especifican en orden. En la notación nominal, el nombre de cada argumento se especifica utilizando := o => para separarlo de la expresión del argumento. De manera similar a la llamada a funciones, descrita en Section 4.3, también se permite mezclar la notación posicional y nominal.

Ejemplos (estos utilizan los ejemplos de declaración de cursor anteriores):

OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 42);
OPEN curs3(key => 42);

Dado que la sustitución de variables se realiza en la consulta de un cursor vinculado, realmente hay dos formas de pasar valores al cursor: con un argumento explícito en OPEN, o implícitamente haciendo referencia a una variable de PL/pgSQL en la consulta. Sin embargo, solo las variables declaradas antes de declarar el cursor vinculado se sustituirán en él. En cualquier caso, el valor a pasar se determina en el momento del OPEN. Por ejemplo, otra forma de obtener el mismo efecto que el ejemplo de curs3 anterior es:

DECLARE
    key integer;
    curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key;
BEGIN
    key := 42;
    OPEN curs4;

41.7.3. Uso de cursores #

Una vez abierto un cursor, este se puede manipular con las instrucciones que se describen aquí.

Estas manipulaciones no necesitan ocurrir en la misma función que abrió el cursor inicialmente. Puedes devolver un valor refcursor desde una función y permitir que el llamador opere en el cursor. (Internamente, un valor refcursor es simplemente el nombre en cadena del portal que contiene la consulta activa para el cursor. Este nombre se puede pasar, asignar a otras variables refcursor, etc., sin alterar el portal).

Todos los portales se cierran implícitamente al final de la transacción. Por lo tanto, un valor refcursor solo es utilizable para hacer referencia a un cursor abierto hasta el final de la transacción.

41.7.3.1. FETCH #

FETCH [ direction { FROM | IN } ] cursor INTO target;

FETCH recupera la siguiente fila (en la dirección indicada) desde el cursor hacia un objetivo, que puede ser una variable de fila, una variable de registro o una lista de variables simples separadas por comas, al igual que SELECT INTO. Si no hay una fila adecuada, el objetivo se establece en NULL(s). Al igual que con SELECT INTO, la variable especial FOUND se puede comprobar para ver si se obtuvo una fila o no. Si no se obtiene ninguna fila, el cursor se posiciona después de la última fila o antes de la primera fila, según la dirección del movimiento.

La cláusula direction puede ser cualquiera de las variantes permitidas en el comando SQL FETCH, excepto las que pueden recuperar más de una fila; a saber, puede ser NEXT, PRIOR, FIRST, LAST, ABSOLUTE count, RELATIVE count, FORWARD o BACKWARD. Omitir la direction es lo mismo que especificar NEXT. En las formas que utilizan un count, este puede ser cualquier expresión con valor entero (a diferencia del comando SQL FETCH, que solo permite una constante entera). Los valores de direction que requieren moverse hacia atrás probablemente fallarán a menos que el cursor se haya declarado o abierto con la opción SCROLL.

El cursor debe ser el nombre de una variable refcursor que haga referencia a un portal de cursor abierto.

Ejemplos:

FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;
FETCH LAST FROM curs3 INTO x, y;
FETCH RELATIVE -2 FROM curs4 INTO x;

41.7.3.2. MOVE #

MOVE [ direction { FROM | IN } ] cursor;

MOVE vuelve a posicionar un cursor sin recuperar ningún dato. MOVE funciona como el comando FETCH, excepto que solo vuelve a posicionar el cursor y no devuelve la fila a la que se movió. La cláusula direction puede ser cualquiera de las variantes permitidas en el comando SQL FETCH, incluyendo aquellas que pueden recuperar más de una fila; el cursor se posiciona en la última de esas filas. (Sin embargo, el caso en el que la cláusula direction es simplemente una expresión count sin palabra clave está en desuso en PL/pgSQL. Esa sintaxis es ambigua con el caso donde la cláusula direction se omite por completo y, por lo tanto, puede fallar si el count no es una constante). Al igual que con SELECT INTO, la variable especial FOUND se puede comprobar para ver si había una fila a la que moverse. Si no existe tal fila, el cursor se posiciona después de la última fila o antes de la primera fila, según la dirección del movimiento.

Ejemplos:

MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;

41.7.3.3. UPDATE/DELETE WHERE CURRENT OF #

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

Cuando un cursor se posiciona en una fila de tabla, esa fila se puede actualizar o eliminar utilizando el cursor para identificarla. Hay restricciones sobre lo que puede ser la consulta del cursor (en particular, no agrupamiento) y es mejor utilizar FOR UPDATE en el cursor. Para obtener más información, consulta la página de referencia de DECLARE.

Un ejemplo:

UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;

41.7.3.4. CLOSE #

CLOSE cursor;

CLOSE cierra el portal subyacente a un cursor abierto. Esto se puede utilizar para liberar recursos antes del final de la transacción, o para liberar la variable de cursor para que se pueda abrir de nuevo.

Un ejemplo:

CLOSE curs1;

41.7.3.5. Retorno de cursores #

Las funciones de PL/pgSQL pueden devolver cursores al llamador. Esto es útil para devolver múltiples filas o columnas, especialmente con conjuntos de resultados muy grandes. Para hacer esto, la función abre el cursor y devuelve el nombre del cursor al llamador (o simplemente abre el cursor utilizando un nombre de portal especificado por o conocido de otra manera por el llamador). El llamador puede entonces recuperar (fetch) filas del cursor. El cursor puede ser cerrado por el llamador, o se cerrará automáticamente cuando se cierre la transacción.

El nombre del portal utilizado para un cursor lo puede especificar el programador o generarse automáticamente. Para especificar un nombre de portal, simplemente asigna una cadena a la variable refcursor antes de abrirla. El valor de cadena de la variable refcursor será utilizado por OPEN como el nombre del portal subyacente. Sin embargo, si el valor de la variable refcursor es nulo (como lo será por defecto), entonces OPEN genera automáticamente un nombre que no entra en conflicto con ningún portal existente y lo asigna a la variable refcursor.

Note

Antes de PostgreSQL 16, las variables de cursor vinculadas se inicializaban para contener sus propios nombres, en lugar de dejarse como nulas, de modo que el nombre del portal subyacente sería el mismo que el nombre de la variable de cursor por defecto. Esto se cambió porque creaba demasiado riesgo de conflictos entre cursores con nombres similares en diferentes funciones.

El siguiente ejemplo muestra una forma en que el llamador puede suministrar un nombre de cursor:

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;

El siguiente ejemplo utiliza la generación automática de nombres de cursor:

CREATE FUNCTION reffunc2() RETURNS refcursor AS '
DECLARE
    ref refcursor;
BEGIN
    OPEN ref FOR SELECT col FROM test;
    RETURN ref;
END;
' LANGUAGE plpgsql;

-- se necesita estar en una transacción para usar cursores.
BEGIN;
SELECT reffunc2();

      reffunc2
--------------------
 <unnamed cursor 1>
(1 row)

FETCH ALL IN "<unnamed cursor 1>";
COMMIT;

El siguiente ejemplo muestra una forma de devolver múltiples cursores desde una sola función:

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;

-- se necesita estar en una transacción para usar cursores.
BEGIN;

SELECT * FROM myfunc('a', 'b');

FETCH ALL FROM a;
FETCH ALL FROM b;
COMMIT;

41.7.4. Bucle a través del resultado de un cursor #

Existe una variante de la instrucción FOR que permite iterar a través de las filas devueltas por un cursor. La sintaxis es:

[ <<label>> ]
FOR recordvar IN bound_cursorvar [ ( [ argument_name { := | => } ] argument_value [, ...] ) ] LOOP
    statements
END LOOP [ label ];

La variable de cursor debe haberse vinculado a alguna consulta cuando se declaró, y no puede estar ya abierta. La instrucción FOR abre automáticamente el cursor, y lo vuelve a cerrar cuando se sale del bucle. Debe aparecer una lista de expresiones de valores de argumentos reales si y solo si el cursor fue declarado para recibir argumentos. Estos valores se sustituirán en la consulta, de la misma manera que durante un OPEN (consulta la Section 41.7.2.3).

La variable recordvar se define automáticamente como tipo record y existe solo dentro del bucle (cualquier definición existente del nombre de la variable se ignora dentro del bucle). Cada fila devuelta por el cursor se asigna sucesivamente a esta variable de registro y se ejecuta el cuerpo del bucle.