41.6. Estructuras de control #

41.6.1. Retorno desde una función
41.6.2. Retorno desde un procedimiento
41.6.3. Llamada a un procedimiento
41.6.4. Conditionals
41.6.5. Bucles simples
41.6.6. Bucle a través de los resultados de una consulta
41.6.7. Bucle a través de arrays
41.6.8. Captura de errores
41.6.9. Obtención de información sobre la ubicación de ejecución

Las estructuras de control son probablemente la parte más útil (e importante) de PL/pgSQL. Con las estructuras de control de PL/pgSQL, puedes manipular los datos de PostgreSQL de una manera muy flexible y potente.

41.6.1. Retorno desde una función #

Hay dos comandos disponibles que te permiten devolver datos de una función: RETURN y RETURN NEXT.

41.6.1.1. RETURN #

RETURN expresión;

RETURN con una expresión termina la función y devuelve el valor de expresión al llamador. Esta forma se utiliza para funciones de PL/pgSQL que no devuelven un conjunto (set).

En una función que devuelve un tipo escalar, el resultado de la expresión se convertirá automáticamente al tipo de retorno de la función, como se describe para las asignaciones. Pero para devolver un valor compuesto (fila), debes escribir una expresión que entregue exactamente el conjunto de columnas solicitado. Esto puede requerir el uso de una conversión explícita.

Si declaraste la función con parámetros de salida, escribe simplemente RETURN sin expresión. Se devolverán los valores actuales de las variables de los parámetros de salida.

Si declaraste la función para que devuelva void, se puede usar una instrucción RETURN para salir de la función antes de tiempo; pero no escribas una expresión después de RETURN.

El valor de retorno de una función no puede dejarse indefinido. Si el control llega al final del bloque de nivel superior de la función sin encontrar una instrucción RETURN, se producirá un error en tiempo de ejecución. Sin embargo, esta restricción no se aplica a las funciones con parámetros de salida ni a las funciones que devuelven void. En esos casos, se ejecuta automáticamente una instrucción RETURN si finaliza el bloque de nivel superior.

Algunos ejemplos:

-- funciones que devuelven un tipo escalar
RETURN 1 + 2;
RETURN scalar_var;

-- funciones que devuelven un tipo compuesto
RETURN composite_type_var;
RETURN (1, 2, 'three'::text);  -- se deben convertir las columnas a los tipos correctos

41.6.1.2. RETURN NEXT y RETURN QUERY #

RETURN NEXT expresión;
RETURN QUERY consulta;
RETURN QUERY EXECUTE cadena_comando [ USING expresión [, ... ] ];

Cuando una función de PL/pgSQL se declara para devolver SETOF sometipo, el procedimiento a seguir es ligeramente diferente. En ese caso, los elementos individuales a devolver se especifican mediante una secuencia de comandos RETURN NEXT o RETURN QUERY, y luego se utiliza un comando RETURN final sin argumento para indicar que la función ha terminado de ejecutarse. RETURN NEXT se puede utilizar tanto con tipos de datos escalares como compuestos; con un tipo de resultado compuesto, se devolverá una tabla completa de resultados. RETURN QUERY añade los resultados de ejecutar una consulta al conjunto de resultados de la función. RETURN NEXT y RETURN QUERY se pueden mezclar libremente en una sola función que devuelva un conjunto, en cuyo caso sus resultados se concatenarán.

RETURN NEXT y RETURN QUERY en realidad no retornan de la función — simplemente añaden cero o más filas al conjunto de resultados de la función. La ejecución continúa con la siguiente instrucción en la función de PL/pgSQL. A medida que se ejecutan comandos sucesivos RETURN NEXT o RETURN QUERY, el conjunto de resultados se va construyendo. Un RETURN final, que no debe tener ningún argumento, hace que el control salga de la función (o puedes simplemente dejar que el control llegue al final de la función).

RETURN QUERY tiene una variante RETURN QUERY EXECUTE, que especifica la consulta a ejecutar dinámicamente. Las expresiones de parámetros se pueden insertar en la cadena de consulta calculada a través de USING, exactamente de la misma manera que en el comando EXECUTE.

Si declaraste la función con parámetros de salida, escribe simplemente RETURN NEXT sin expresión. En cada ejecución, los valores actuales de las variables de los parámetros de salida se guardarán para su eventual retorno como una fila del resultado. Ten en cuenta que debes declarar que la función devuelve SETOF record cuando hay múltiples parámetros de salida, o SETOF sometipo cuando hay solo un parámetro de salida de tipo sometipo, con el fin de crear una función que devuelva un conjunto con parámetros de salida.

Aquí tienes un ejemplo de una función que utiliza RETURN NEXT:

CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');

CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$BODY$
DECLARE
    r foo%rowtype;
BEGIN
    FOR r IN
        SELECT * FROM foo WHERE fooid > 0
    LOOP
        -- se puede realizar algún procesamiento aquí
        RETURN NEXT r; -- devolver la fila actual de SELECT
    END LOOP;
    RETURN;
END;
$BODY$
LANGUAGE plpgsql;

SELECT * FROM get_all_foo();

Aquí tienes un ejemplo de una función que utiliza RETURN QUERY:

CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$BODY$
BEGIN
    RETURN QUERY SELECT flightid
                   FROM flight
                  WHERE flightdate >= $1
                    AND flightdate < ($1 + 1);

    -- Como la ejecución no ha terminado, podemos comprobar si se devolvieron filas
    -- y lanzar una excepción si no es así.
    IF NOT FOUND THEN
        RAISE EXCEPTION 'No hay vuelos en %.', $1;
    END IF;

    RETURN;
 END;
$BODY$
LANGUAGE plpgsql;

-- Devuelve vuelos disponibles o lanza una excepción si no hay vuelos disponibles.
SELECT * FROM get_available_flightid(CURRENT_DATE);

Note

La implementación actual de RETURN NEXT y RETURN QUERY almacena todo el conjunto de resultados antes de retornar de la función, como se analizó anteriormente. Eso significa que si una función de PL/pgSQL produce un conjunto de resultados muy grande, el rendimiento podría ser deficiente: los datos se escribirán en el disco para evitar el agotamiento de la memoria, pero la función en sí no retornará hasta que se haya generado todo el conjunto de resultados. Una versión futura de PL/pgSQL podría permitir a los usuarios definir funciones que devueven conjuntos que no tengan esta limitación. Actualmente, el punto en el que los datos comienzan a escribirse en el disco está controlado por la variable de configuración work_mem. Los administradores que tengan suficiente memoria para almacenar conjuntos de resultados más grandes en memoria deberían considerar aumentar este parámetro.

41.6.2. Retorno desde un procedimiento #

Un procedimiento no tiene un valor de retorno. Por lo tanto, un procedimiento puede finalizar sin una instrucción RETURN. Si deseas utilizar una instrucción RETURN para salir del código antes de tiempo, escribe simplemente RETURN sin expresión.

Si el procedimiento tiene parámetros de salida, los valores finales de las variables de los parámetros de salida se devolverán al llamador.

41.6.3. Llamada a un procedimiento #

Una función de PL/pgSQL, procedimiento o bloque DO puede llamar a un procedimiento utilizando CALL. Los parámetros de salida se manejan de manera diferente a como funciona CALL en SQL plano. Cada parámetro OUT o INOUT del procedimiento debe corresponder a una variable en la instrucción CALL, y lo que devuelva el procedimiento se asignará de nuevo a esa variable después de que retorne. Por ejemplo:

CREATE PROCEDURE triple(INOUT x int)
LANGUAGE plpgsql
AS $$
BEGIN
    x := x * 3;
END;
$$;

DO $$
DECLARE myvar int := 5;
BEGIN
  CALL triple(myvar);
  RAISE NOTICE 'myvar = %', myvar;  -- imprime 15
END;
$$;

The variable corresponding to an output parameter can be a simple variable or a field of a composite-type variable. Currently, it cannot be an element of an array.

41.6.4. Conditionals #

IF and CASE statements let you execute alternative commands based on certain conditions. PL/pgSQL has three forms of IF:

  • IF ... THEN ... END IF

  • IF ... THEN ... ELSE ... END IF

  • IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF

and two forms of CASE:

  • CASE ... WHEN ... THEN ... ELSE ... END CASE

  • CASE WHEN ... THEN ... ELSE ... END CASE

41.6.4.1. IF-THEN #

IF boolean-expression THEN
    statements
END IF;

IF-THEN statements are the simplest form of IF. The statements between THEN and END IF will be executed if the condition is true. Otherwise, they are skipped.

Example:

IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;

41.6.4.2. IF-THEN-ELSE #

IF boolean-expression THEN
    statements
ELSE
    statements
END IF;

Las instrucciones IF-THEN-ELSE añaden funcionalidad a IF-THEN al permitirte especificar un conjunto alternativo de instrucciones que se deben ejecutar si la condición no es verdadera. (Ten en cuenta que esto incluye el caso en el que la condición se evalúa como NULL).

Ejemplos:

IF parentid IS NULL OR parentid = ''
THEN
    RETURN fullname;
ELSE
    RETURN hp_true_filename(parentid) || '/' || fullname;
END IF;

IF v_count > 0 THEN
    INSERT INTO users_count (count) VALUES (v_count);
    RETURN 't';
ELSE
    RETURN 'f';
END IF;

41.6.4.3. IF-THEN-ELSIF #

IF boolean-expression THEN
    statements
[ ELSIF boolean-expression THEN
    statements
[ ELSIF boolean-expression THEN
    statements
    ...
]
]
[ ELSE
    statements ]
END IF;

A veces hay más de dos alternativas. IF-THEN-ELSIF proporciona un método conveniente para comprobar varias alternativas una tras otra. Las condiciones IF se prueban sucesivamente hasta encontrar la primera que sea verdadera. Luego se ejecutan las instrucciones asociadas, tras lo cual el control pasa a la siguiente instrucción después de END IF. (Las condiciones IF subsiguientes no se evalúan). Si ninguna de las condiciones IF es verdadera, se ejecuta el bloque ELSE (si existe).

Aquí tienes un ejemplo:

IF number = 0 THEN
    result := 'zero';
ELSIF number > 0 THEN
    result := 'positive';
ELSIF number < 0 THEN
    result := 'negative';
ELSE
    -- hmm, la única otra posibilidad es que number sea null
    result := 'NULL';
END IF;

La palabra clave ELSIF también se puede escribir como ELSEIF.

Una forma alternativa de lograr la misma tarea es anidar instrucciones IF-THEN-ELSE, como en el siguiente ejemplo:

IF demo_row.sex = 'm' THEN
    pretty_sex := 'man';
ELSE
    IF demo_row.sex = 'f' THEN
        pretty_sex := 'woman';
    END IF;
END IF;

Sin embargo, este método requiere escribir un END IF correspondiente para cada IF, por lo que es mucho más engorroso que usar ELSIF cuando hay muchas alternativas.

41.6.4.4. CASE simple #

CASE search-expression
    WHEN expression [, expression [ ... ]] THEN
      statements
  [ WHEN expression [, expression [ ... ]] THEN
      statements
    ... ]
  [ ELSE
      statements ]
END CASE;

La forma simple de CASE proporciona una ejecución condicional basada en la igualdad de los operandos. La search-expression se evalúa (una vez) y se compara sucesivamente con cada expression en las cláusulas WHEN. Si se encuentra una coincidencia, se ejecutan las correspondientes statements, y luego el control pasa a la siguiente instrucción después de END CASE. (Las expresiones WHEN subsiguientes no se evalúan). Si no se encuentra ninguna coincidencia, se ejecutan las statements de ELSE; pero si no hay ELSE, se lanza una excepción CASE_NOT_FOUND.

Aquí tienes un ejemplo simple:

CASE x
    WHEN 1, 2 THEN
        msg := 'one or two';
    ELSE
        msg := 'other value than one or two';
END CASE;

41.6.4.5. CASE de búsqueda #

CASE
    WHEN boolean-expression THEN
      statements
  [ WHEN boolean-expression THEN
      statements
    ... ]
  [ ELSE
      statements ]
END CASE;

La forma de búsqueda de CASE proporciona una ejecución condicional basada en la veracidad de expresiones booleanas. La boolean-expression de cada cláusula WHEN se evalúa a su vez, hasta encontrar una que resulte verdadera (true). Luego se ejecutan las correspondientes statements, y luego el control pasa a la siguiente instrucción después de END CASE. pero si no hay ELSE, se lanza una excepción CASE_NOT_FOUND.

41.6.5. Bucles simples #

Con las instrucciones LOOP, EXIT, CONTINUE, WHILE, FOR y FOREACH, puedes hacer que tu función PL/pgSQL repita una serie de comandos.

41.6.5.1. LOOP #

[ <<label>> ]
LOOP
    statements
END LOOP [ label ];

LOOP define un bucle incondicional que se repite indefinidamente hasta que termina mediante una instrucción EXIT o RETURN. La etiqueta opcional label puede ser utilizada por las instrucciones EXIT y CONTINUE dentro de bucles anidados para especificar a qué bucle se refieren esas instrucciones.

41.6.5.2. EXIT #

EXIT [ label ] [ WHEN boolean-expression ];

Si no se proporciona ninguna label, se termina el bucle más interno y se ejecuta a continuación la instrucción que sigue a END LOOP. Si se proporciona una label, esta debe ser la etiqueta del bucle o bloque actual o de algún nivel externo de bucles o bloques anidados. Luego, se termina el bucle o bloque nombrado y el control continúa con la instrucción que sigue al END correspondiente del bucle/bloque.

Si se especifica WHEN, la salida del bucle ocurre solo si boolean-expression es verdadera. De lo contrario, el control pasa a la instrucción que sigue a EXIT.

EXIT se puede usar con todo tipo de bucles; no está limitado al uso con bucles incondicionales.

Cuando se usa con un bloque BEGIN, EXIT pasa el control a la siguiente instrucción después del final del bloque. Ten en cuenta que se debe utilizar una etiqueta para este propósito; un EXIT sin etiqueta nunca se considerará que coincide con un bloque BEGIN. (Este es un cambio con respecto a las versiones anteriores a la 8.4 de PostgreSQL, que permitían que un EXIT sin etiqueta coincidiera con un bloque BEGIN).

Ejemplos:

LOOP
    -- algunos cálculos
    IF count > 0 THEN
        EXIT;  -- salir del bucle
    END IF;
END LOOP;

LOOP
    -- algunos cálculos
    EXIT WHEN count > 0;  -- mismo resultado que el ejemplo anterior
END LOOP;

<<ablock>>
BEGIN
    -- algunos cálculos
    IF stocks > 100000 THEN
        EXIT ablock;  -- provoca la salida del bloque BEGIN
    END IF;
    -- los cálculos aquí se omitirán cuando stocks > 100000
END;

41.6.5.3. CONTINUE #

CONTINUE [ label ] [ WHEN boolean-expression ];

Si no se proporciona ninguna label, se comienza la siguiente iteración del bucle más interno. Es decir, se omiten todas las instrucciones restantes en el cuerpo del bucle y el control vuelve a la expresión de control del bucle (si existe) para determinar si se necesita otra iteración del bucle. Si la label está presente, especifica la etiqueta del bucle cuya ejecución continuará.

Si se especifica WHEN, la siguiente iteración del bucle comienza solo si boolean-expression es verdadera. De lo contrario, el control pasa a la instrucción después de CONTINUE.

CONTINUE se puede usar con todo tipo de bucles; no está limitado al uso con bucles incondicionales.

Ejemplos:

LOOP
    -- algunos cálculos
    EXIT WHEN count > 100;
    CONTINUE WHEN count < 50;
    -- algunos cálculos para count EN [50 .. 100]
END LOOP;

41.6.5.4. WHILE #

[ <<label>> ]
WHILE boolean-expression LOOP
    statements
END LOOP [ label ];

La instrucción WHILE repite una secuencia de instrucciones mientras la boolean-expression se evalúe como verdadera. La expresión se comprueba justo antes de cada entrada al cuerpo del bucle.

Por ejemplo:

WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- algunos cálculos aquí
END LOOP;

WHILE NOT done LOOP
    -- algunos cálculos aquí
END LOOP;

41.6.5.5. FOR (variante entera) #

[ <<label>> ]
FOR name IN [ REVERSE ] expression .. expression [ BY expression ] LOOP
    statements
END LOOP [ label ];

Esta forma de FOR crea un bucle que itera sobre un rango de valores enteros. La variable name se define automáticamente como tipo integer y existe solo dentro del bucle (cualquier definición existente del nombre de la variable se ignora dentro del bucle). Las dos expresiones que dan los límites inferior y superior del rango se evalúan una vez al entrar al bucle. Si no se especifica la cláusula BY, el paso de iteración es 1; de lo contrario, es el valor especificado en la cláusula BY, que nuevamente se evalúa una vez al entrar al bucle. Si se especifica REVERSE, el valor del paso se resta, en lugar de sumarse, después de cada iteración.

Algunos ejemplos de bucles FOR de enteros:

FOR i IN 1..10 LOOP
    -- i tomará los valores 1,2,3,4,5,6,7,8,9,10 dentro del bucle
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- i tomará los valores 10,9,8,7,6,5,4,3,2,1 dentro del bucle
END LOOP;

FOR i IN REVERSE 10..1 BY 2 LOOP
    -- i tomará los valores 10,8,6,4,2 dentro del bucle
END LOOP;

Si el límite inferior es mayor que el límite superior (o menor, en el caso de REVERSE), el cuerpo del bucle no se ejecuta en absoluto. No se genera ningún error.

Si se adjunta una label al bucle FOR, se puede hacer referencia a la variable del bucle entero con un nombre calificado, utilizando esa label.

41.6.6. Bucle a través de los resultados de una consulta #

Utilizando un tipo diferente de bucle FOR, puedes iterar a través de los resultados de una consulta y manipular esos datos en consecuencia. La sintaxis es:

[ <<label>> ]
FOR target IN query LOOP
    statements
END LOOP [ label ];

El target es una variable de registro (record), variable de fila (row) o una lista separada por comas de variables escalares. Al target se le asigna sucesivamente cada fila resultante de la query y se ejecuta el cuerpo del bucle para cada fila. Aquí tienes un ejemplo:

CREATE FUNCTION refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    RAISE NOTICE 'Refreshing all materialized views...';

    FOR mviews IN
       SELECT n.nspname AS mv_schema,
              c.relname AS mv_name,
              pg_catalog.pg_get_userbyid(c.relowner) AS owner
         FROM pg_catalog.pg_class c
    LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
         WHERE c.relkind = 'm'
      ORDER BY 1
    LOOP

        -- Ahora "mviews" tiene un registro con información sobre la vista materializada

        RAISE NOTICE 'Refreshing materialized view %.% (owner: %)...',
                     quote_ident(mviews.mv_schema),
                     quote_ident(mviews.mv_name),
                     quote_ident(mviews.owner);
        EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name);
    END LOOP;

    RAISE NOTICE 'Done refreshing materialized views.';
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

Si el bucle termina con una instrucción EXIT, el último valor de fila asignado seguirá siendo accesible después del bucle.

La query utilizada en este tipo de instrucción FOR puede ser cualquier comando SQL que devuelva filas al llamador: SELECT es el caso más común, pero también puedes usar INSERT, UPDATE, DELETE o MERGE con una cláusula RETURNING. Algunos comandos de utilidad como EXPLAIN también funcionarán.

Las variables de PL/pgSQL se reemplazan por parámetros de consulta, y el plan de consulta se almacena en caché para su posible reutilización, como se analiza en detalle en Section 41.11.1 y Section 41.11.2.

La instrucción FOR-IN-EXECUTE es otra forma de iterar sobre filas:

[ <<label>> ]
FOR target IN EXECUTE text_expression [ USING expression [, ... ] ] LOOP
    statements
END LOOP [ label ];

Esto es similar a la forma anterior, excepto que la consulta de origen se especifica como una expresión de cadena, que se evalúa y se vuelve a planificar en cada entrada al bucle FOR. Esto te permite elegir la velocidad de una consulta preplanificada o la flexibilidad de una consulta dinámica, al igual que con una instrucción EXECUTE simple. Al igual que con EXECUTE, los valores de los parámetros se pueden insertar en el comando dinámico a través de USING.

Otra forma de especificar la consulta cuyos resultados se deben iterar es declararla como un cursor. Esto se describe en Section 41.7.4.

41.6.7. Bucle a través de arrays #

El bucle FOREACH es muy similar a un bucle FOR, pero en lugar de iterar a través de las filas devueltas por una consulta SQL, itera a través de los elementos de un valor de tipo array. (En general, FOREACH está pensado para iterar a través de los componentes de una expresión con valor compuesto; en el futuro se podrían añadir variantes para iterar a través de otros compuestos además de los arrays). La instrucción FOREACH para iterar sobre un array es:

[ <<label>> ]
FOREACH target [ SLICE number ] IN ARRAY expression LOOP
    statements
END LOOP [ label ];

Sin SLICE, o si se especifica SLICE 0, el bucle itera a través de los elementos individuales del array producido al evaluar la expression. A la variable target se le asigna cada valor de elemento en secuencia, y el cuerpo del bucle se ejecuta para cada elemento. Aquí tienes un ejemplo de bucle a través de los elementos de un array de enteros:

CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
  s int8 := 0;
  x int;
BEGIN
  FOREACH x IN ARRAY $1
  LOOP
    s := s + x;
  END LOOP;
  RETURN s;
END;
$$ LANGUAGE plpgsql;

Los elementos se recorren en el orden de almacenamiento, independientemente del número de dimensiones del array. Aunque el target suele ser una sola variable, puede ser una lista de variables cuando se itera a través de un array de valores compuestos (registros). En ese caso, para cada elemento del array, las variables se asignan a partir de las columnas sucesivas del valor compuesto.

Con un valor SLICE positivo, FOREACH itera a través de rodajas (slices) del array en lugar de elementos individuales. El valor SLICE debe ser una constante entera no mayor que el número de dimensiones del array. La variable target debe ser un array, y recibe rodajas sucesivas del valor del array, donde cada rodaja tiene el número de dimensiones especificado por SLICE. Aquí tienes un ejemplo de iteración a través de rodajas unidimensionales:

CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
DECLARE
  x int[];
BEGIN
  FOREACH x SLICE 1 IN ARRAY $1
  LOOP
    RAISE NOTICE 'row = %', x;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);

NOTICE:  row = {1,2,3}
NOTICE:  row = {4,5,6}
NOTICE:  row = {7,8,9}
NOTICE:  row = {10,11,12}

41.6.8. Captura de errores #

Por defecto, cualquier error que ocurra en una función PL/pgSQL aborta la ejecución de la función y de la transacción circundante. Puedes capturar errores y recuperarte de ellos utilizando un bloque BEGIN con una cláusula EXCEPTION. La sintaxis es una extensión de la sintaxis normal para un bloque BEGIN:

[ <<label>> ]
[ DECLARE
    declarations ]
BEGIN
    statements
EXCEPTION
    WHEN condition [ OR condition ... ] THEN
        handler_statements
    [ WHEN condition [ OR condition ... ] THEN
          handler_statements
      ... ]
END;

Si no ocurre ningún error, esta forma de bloque simplemente ejecuta todas las statements, y luego el control pasa a la siguiente instrucción después de END. Pero si ocurre un error dentro de las statements, se abandona el procesamiento posterior de las statements, y el control pasa a la lista EXCEPTION. Se busca en la lista la primera condition que coincida con el error ocurrido. Si se encuentra una coincidencia, se ejecutan las correspondientes handler_statements, y luego el control pasa a la siguiente instrucción después de END. Si no se encuentra ninguna coincidencia, el error se propaga hacia afuera como si la cláusula EXCEPTION no estuviera allí en absoluto: el error puede ser capturado por un bloque contenedor con EXCEPTION, o si no hay ninguno, aborta el procesamiento de la función.

Los nombres de las condition pueden ser cualquiera de los que se muestran en Appendix A. Un nombre de categoría coincide con cualquier error dentro de su categoría. El nombre de condición especial OTHERS coincide con todos los tipos de error excepto QUERY_CANCELED y ASSERT_FAILURE. (Es posible, pero a menudo desaconsejable, capturar esos dos tipos de error por su nombre). Los nombres de las condiciones no distinguen entre mayúsculas y minúsculas. Además, se puede especificar una condición de error mediante el código SQLSTATE; por ejemplo, estos son equivalentes:

WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...

Si ocurre un nuevo error dentro de las handler_statements seleccionadas, este no puede ser capturado por esta cláusula EXCEPTION, sino que se propaga hacia afuera. Una cláusula EXCEPTION contenedora podría capturarlo.

Cuando un error es capturado por una cláusula EXCEPTION, las variables locales de la función PL/pgSQL permanecen tal como estaban cuando ocurrió el error, pero todos los cambios realizados en el estado persistente de la base de datos dentro del bloque se revierten. Como ejemplo, considera este fragmento:

INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
BEGIN
    UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
    x := x + 1;
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE 'caught division_by_zero';
        RETURN x;
END;

Cuando el control llega a la asignación de y, fallará con un error division_by_zero. Esto será capturado por la cláusula EXCEPTION. El valor devuelto en la instrucción RETURN será el valor incrementado de x, pero los efectos del comando UPDATE se habrán revertido. Sin embargo, el comando INSERT que precede al bloque no se revierte, por lo que el resultado final es que la base de datos contiene a Tom Jones y no a Joe Jones.

Tip

Un bloque que contiene una cláusula EXCEPTION es significativamente más costoso de entrar y salir que un bloque sin ella. Por lo tanto, no uses EXCEPTION a menos que sea necesario.

Example 41.2. Excepciones con UPDATE/INSERT

Este ejemplo utiliza el manejo de excepciones para realizar un UPDATE o un INSERT, según corresponda. Se recomienda que las aplicaciones utilicen INSERT con ON CONFLICT DO UPDATE en lugar de utilizar este patrón. Este ejemplo sirve principalmente para ilustrar el uso de las estructuras de control de flujo de PL/pgSQL:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- primero intentamos actualizar la clave
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- no está allí, así que intentamos insertar la clave
        -- si alguien más inserta la misma clave de forma concurrente,
        -- podríamos obtener un fallo de clave única
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- No hacer nada, y volver al bucle para intentar el UPDATE de nuevo.
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

Esta codificación asume que el error unique_violation es causado por el INSERT, y no por, digamos, un INSERT en una función disparadora (trigger) en la tabla. También podría comportarse mal si hay más de un índice único en la tabla, ya que reintentará la operation independientemente de qué índice causó el error. Se podría tener más seguridad utilizando las características que se analizan a continuación para comprobar que el error capturado era el esperado.


41.6.8.1. Obtención de información sobre un error #

Los manejadores de excepciones a menudo necesitan identificar el error específico que ocurrió. Hay dos formas de obtener información sobre la excepción actual en PL/pgSQL: variables especiales y el comando

Dentro de un manejador de excepciones, la variable especial SQLSTATE contiene el código de error que corresponde a la excepción lanzada (consulta la Table A.1 para ver una lista de los posibles códigos de error). La variable especial SQLERRM contiene el mensaje de error asociado a la excepción. Estas variables no están definidas fuera de los manejadores de excepciones.

Dentro de un manejador de excepciones, también puedes obtener información sobre la excepción actual mediante el comando GET STACKED DIAGNOSTICS, que tiene la forma:

GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ];

Cada item es una palabra clave que identifica un valor de estado que será asignado a la variable especificada (que debe ser del tipo de datos adecuado para recibirlo). Los elementos de estado actualmente disponibles se muestran en Table 41.2.

Table 41.2. Elementos de diagnóstico de errores

NombreTipoDescripción
RETURNED_SQLSTATEtextel código de error SQLSTATE de la excepción
COLUMN_NAMEtextel nombre de la columna relacionada con la excepción
CONSTRAINT_NAMEtextel nombre de la restricción relacionada con la excepción
PG_DATATYPE_NAMEtextel nombre del tipo de datos relacionado con la excepción
MESSAGE_TEXTtextel texto del mensaje principal de la excepción
TABLE_NAMEtextel nombre de la tabla relacionada con la excepción
SCHEMA_NAMEtextel nombre del esquema relacionado con la excepción
PG_EXCEPTION_DETAILtextel texto del mensaje de detalle de la excepción, si lo hay
PG_EXCEPTION_HINTtextel texto del mensaje de sugerencia (hint) de la excepción, si lo hay
PG_EXCEPTION_CONTEXTtextlínea(s) de texto que describen la pila de llamadas en el momento de la excepción (consulta la Section 41.6.9)

Si la excepción no estableció un valor para un elemento, se devolverá una cadena vacía.

Aquí tienes un ejemplo:

DECLARE
  text_var1 text;
  text_var2 text;
  text_var3 text;
BEGIN
  -- algún procesamiento que podría causar una excepción
  ...
EXCEPTION WHEN OTHERS THEN
  GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                          text_var2 = PG_EXCEPTION_DETAIL,
                          text_var3 = PG_EXCEPTION_HINT;
END;

41.6.9. Obtención de información sobre la ubicación de ejecución #

El comando GET DIAGNOSTICS, descrito previamente en Section 41.5.5, recupera información sobre el estado de ejecución actual (mientras que el comando GET STACKED DIAGNOSTICS analizado anteriormente informa sobre la información del estado de ejecución a partir de un error anterior). Su elemento de estado PG_CONTEXT es útil para identificar la ubicación de ejecución actual. PG_CONTEXT devuelve una cadena de texto con línea(s) de texto que describen la pila de llamadas. La primera línea se refiere a la función actual y al comando GET DIAGNOSTICS actualmente en ejecución. La segunda y las líneas siguientes se refieren a las funciones llamadoras más arriba en la pila de llamadas. Por ejemplo:

CREATE OR REPLACE FUNCTION outer_func() RETURNS integer AS $$
BEGIN
  RETURN inner_func();
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION inner_func() RETURNS integer AS $$
DECLARE
  stack text;
BEGIN
  GET DIAGNOSTICS stack = PG_CONTEXT;
  RAISE NOTICE E'--- Call Stack ---\n%', stack;
  RETURN 1;
END;
$$ LANGUAGE plpgsql;

SELECT outer_func();

NOTICE:  --- Call Stack ---
PL/pgSQL function inner_func() line 5 at GET DIAGNOSTICS
PL/pgSQL function outer_func() line 3 at RETURN
CONTEXT:  PL/pgSQL function outer_func() line 3 at RETURN
 outer_func
 ------------
           1
(1 row)

GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT devuelve el mismo tipo de traza de pila, pero describiendo la ubicación en la que se detectó un error, en lugar de la ubicación actual.