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.
Hay dos comandos disponibles que te permiten devolver datos de una función:
RETURN y RETURN NEXT.
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
RETURN NEXT y RETURN QUERY #RETURN NEXTexpresión; RETURN QUERYconsulta; RETURN QUERY EXECUTEcadena_comando[ USINGexpresión[, ... ] ];
Cuando una función de PL/pgSQL se declara para devolver
SETOF , el procedimiento a seguir es ligeramente
diferente. En ese caso, los elementos individuales a devolver se especifican mediante una secuencia de
comandos sometipoRETURN 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 cuando hay solo un parámetro de salida de tipo
sometiposometipo, 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);
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.
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.
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.
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
IF-THEN #IFboolean-expressionTHENstatementsEND 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;
IF-THEN-ELSE #IFboolean-expressionTHENstatementsELSEstatementsEND 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;
IF-THEN-ELSIF #IFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements... ] ] [ ELSEstatements] 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.
CASE simple #CASEsearch-expressionWHENexpression[,expression[ ... ]] THENstatements[ WHENexpression[,expression[ ... ]] THENstatements... ] [ ELSEstatements] 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;
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.
Con las instrucciones LOOP, EXIT,
CONTINUE, WHILE, FOR y
FOREACH, puedes hacer que tu función PL/pgSQL
repita una serie de comandos.
LOOP #[ <<label>> ] LOOPstatementsEND 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.
EXIT #EXIT [label] [ WHENboolean-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;
CONTINUE #CONTINUE [label] [ WHENboolean-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;
WHILE #[ <<label>> ] WHILEboolean-expressionLOOPstatementsEND 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;
FOR (variante entera) #[ <<label>> ] FORnameIN [ REVERSE ]expression..expression[ BYexpression] LOOPstatementsEND 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.
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>> ] FORtargetINqueryLOOPstatementsEND 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>> ] FORtargetIN EXECUTEtext_expression[ USINGexpression[, ... ] ] LOOPstatementsEND 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.
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>> ] FOREACHtarget[ SLICEnumber] IN ARRAYexpressionLOOPstatementsEND 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}
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>> ] [ DECLAREdeclarations] BEGINstatementsEXCEPTION WHENcondition[ ORcondition... ] THENhandler_statements[ WHENcondition[ ORcondition... ] THENhandler_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.
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.
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 DIAGNOSTICSvariable{ = | := }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
| Nombre | Tipo | Descripción |
|---|---|---|
RETURNED_SQLSTATE | text | el código de error SQLSTATE de la excepción |
COLUMN_NAME | text | el nombre de la columna relacionada con la excepción |
CONSTRAINT_NAME | text | el nombre de la restricción relacionada con la excepción |
PG_DATATYPE_NAME | text | el nombre del tipo de datos relacionado con la excepción |
MESSAGE_TEXT | text | el texto del mensaje principal de la excepción |
TABLE_NAME | text | el nombre de la tabla relacionada con la excepción |
SCHEMA_NAME | text | el nombre del esquema relacionado con la excepción |
PG_EXCEPTION_DETAIL | text | el texto del mensaje de detalle de la excepción, si lo hay |
PG_EXCEPTION_HINT | text | el texto del mensaje de sugerencia (hint) de la excepción, si lo hay |
PG_EXCEPTION_CONTEXT | text | lí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;
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.