En esta sección y en las siguientes, describimos todos los tipos de instrucciones que entiende explícitamente PL/pgSQL. Cualquier cosa que no se reconozca como uno de estos tipos de instrucciones se presume que es un comando SQL y se envía al motor de base de datos principal para su ejecución, como se describe en la Section 41.5.2.
La asignación de un valor a una variable de PL/pgSQL se escribe como:
variable{ := | = }expresión;
Como se explicó anteriormente, la expresión en dicha instrucción se evalúa mediante un comando
SQL SELECT enviado al motor de base de datos principal. La expresión debe
producir un único valor (posiblemente un valor de fila, si la variable es una variable de fila
o de registro). La variable de destino puede ser una variable simple (opcionalmente calificada
con un nombre de bloque), un campo de un destino de fila o registro, o un elemento o sección (slice)
de un destino de array. Se puede usar el signo igual (=) en lugar de
:= compatible con PL/SQL.
Si el tipo de datos del resultado de la expresión no coincide con el tipo de datos de la variable, el valor se convertirá como si se tratara de una conversión de asignación (consulte la Section 10.4). Si no se conoce ninguna conversión de asignación para el par de tipos de datos involucrados, el intérprete de PL/pgSQL intentará convertir el valor del resultado textualmente, es decir, aplicando la función de salida del tipo de resultado seguida de la función de entrada del tipo de variable. Ten en cuenta que esto podría dar lugar a errores en tiempo de ejecución generados por la función de entrada si la forma de cadena del valor del resultado no es aceptable para la función de entrada.
Ejemplos:
tax := subtotal * 0.06; my_record.user_id := 20; my_array[j] := 20; my_array[1:3] := array[1,2,3]; complex_array[n].realpart = 12.3;
En general, any SQL command that does not return rows can be executed within a PL/pgSQL function just by writing the command. Por ejemplo, podrías crear y llenar una tabla escribiendo:
CREATE TABLE mytable (id int primary key, data text); INSERT INTO mytable VALUES (1,'one'), (2,'two');
If the command does return rows (for example SELECT, or
INSERT/UPDATE/DELETE/MERGE
with RETURNING), there are two ways to proceed.
When the command will return at most one row, or you only care about
the first row of output, write the command as usual but add
an INTO clause to capture the output, as described
in Section 41.5.3.
To process all of the output rows, write the command as the data
source for a FOR loop, as described in
Section 41.6.6.
Usually it is not sufficient just to execute statically-defined SQL commands. Typically you'll want a command to use varying data values, or even to vary in more fundamental ways such as by using different table names at different times. Again, there are two ways to proceed depending on the situation.
PL/pgSQL variable values can be
automatically inserted into optimizable SQL commands, which
are SELECT, INSERT,
UPDATE, DELETE,
MERGE, and certain
utility commands that incorporate one of these, such
as EXPLAIN and CREATE TABLE ... AS
SELECT. In these commands,
any PL/pgSQL variable name appearing
in the command text is replaced by a query parameter, and then the
current value of the variable is provided as the parameter value
at run time. This is exactly like the processing described earlier
for expressions; for details see Section 41.11.1.
When executing an optimizable SQL command in this way, PL/pgSQL may cache and re-use the execution plan for the command, as discussed in Section 41.11.2.
Non-optimizable SQL commands (also called utility commands) are not
capable of accepting query parameters. So automatic substitution
of PL/pgSQL variables does not work in such
commands. To include non-constant text in a utility command executed
from PL/pgSQL, you must build the utility
command as a string and then EXECUTE it, as
discussed in Section 41.5.4.
EXECUTE must also be used if you want to modify
the command in some other way than supplying a data value, for example
by changing a table name.
Sometimes it is useful to evaluate an expression or SELECT
query but discard the result, for example when calling a function
that has side-effects but no useful result value. To do
this in PL/pgSQL, use the
PERFORM statement:
PERFORM query;
This executes query and discards the
result. Write the query the same
way you would write an SQL SELECT command, but replace the
initial keyword SELECT with PERFORM.
For WITH queries, use PERFORM and then
place the query in parentheses. (In this case, the query can only
return one row.)
PL/pgSQL variables will be
substituted into the query just as described above,
and the plan is cached in the same way. Also, the special variable
FOUND is set to true if the query produced at
least one row, or false if it produced no rows (see
Section 41.5.5).
One might expect that writing SELECT directly
would accomplish this result, but at
present the only accepted way to do it is
PERFORM. An SQL command that can return rows,
such as SELECT, will be rejected as an error
unless it has an INTO clause as discussed in the
next section.
Un ejemplo:
PERFORM create_mv('cs_session_page_requests_mv', my_query);
El resultado de un comando SQL que produce una sola fila (posiblemente de múltiples columnas) se
puede asignar a una variable de registro, una variable de tipo de fila o una lista de variables
escalares. Esto se hace escribiendo el comando SQL base y agregando una cláusula INTO.
Por ejemplo,
SELECTselect_expressionsINTO [STRICT]destinoFROM ...; INSERT ... RETURNINGexpresionesINTO [STRICT]destino; UPDATE ... RETURNINGexpresionesINTO [STRICT]destino; DELETE ... RETURNINGexpresionesINTO [STRICT]destino; MERGE ... RETURNINGexpresionesINTO [STRICT]destino;
donde el destino puede ser una variable de registro, una variable de fila
o una lista de variables simples y campos de registro/fila separados por comas. Las variables de
PL/pgSQL se sustituirán en el resto del comando (es decir, en todo menos en
la cláusula INTO) tal como se describe anteriormente, y el plan se almacena en caché
de la misma manera. Esto funciona para SELECT,
INSERT/UPDATE/DELETE/MERGE
con RETURNING y ciertos comandos de utilidad que devuelven conjuntos de filas,
como EXPLAIN. A excepción de la cláusula INTO, el comando SQL es
el mismo que se escribiría fuera de PL/pgSQL.
Ten en cuenta que esta interpretación de SELECT con INTO es bastante
diferente del comando regular SELECT INTO de PostgreSQL,
en el cual el destino de INTO es una tabla recién creada. Si deseas crear una tabla a
partir del resultado de un SELECT dentro de una función de
PL/pgSQL, utiliza la sintaxis
CREATE TABLE ... AS SELECT.
If a row variable or a variable list is used as target, the command's result columns must exactly match the structure of the target as to number and data types, or else a run-time error occurs. When a record variable is the target, it automatically configures itself to the row type of the command's result columns.
The INTO clause can appear almost anywhere in the SQL
command. Customarily it is written either just before or just after
the list of select_expressions in a
SELECT command, or at the end of the command for other
command types. It is recommended that you follow this convention
in case the PL/pgSQL parser becomes
stricter in future versions.
If STRICT is not specified in the INTO
clause, then target will be set to the first
row returned by the command, or to nulls if the command returned no rows.
(Note that “the first row” is not
well-defined unless you've used ORDER BY.) Any result rows
after the first row are discarded.
You can check the special FOUND variable (see
Section 41.5.5) to
determine whether a row was returned:
SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
RAISE EXCEPTION 'employee % not found', myname;
END IF;
If the STRICT option is specified, the command must
return exactly one row or a run-time error will be reported, either
NO_DATA_FOUND (no rows) or TOO_MANY_ROWS
(more than one row). You can use an exception block if you wish
to catch the error, for example:
BEGIN
SELECT * INTO STRICT myrec FROM emp WHERE empname = myname;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'employee % not found', myname;
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'employee % not unique', myname;
END;
La ejecución exitosa de un comando con STRICT
siempre establece FOUND en true.
Para INSERT/UPDATE/DELETE/MERGE con
RETURNING, PL/pgSQL reporta
un error si se devuelve más de una fila, incluso cuando no se especifica
STRICT. Esto se debe a que no existe una opción como
ORDER BY con la cual determinar qué fila afectada debe devolverse.
Si print_strict_params está habilitado para la función,
entonces, cuando se genera un error debido a que no se cumplen los requisitos
de STRICT, la sección DETAIL del mensaje
de error incluirá información sobre los parámetros pasados al comando.
Puedes cambiar la configuración de print_strict_params para todas las
funciones configurando plpgsql.print_strict_params, aunque solo se
verán afectadas las compilaciones de funciones posteriores. También puedes habilitarlo
función por función mediante una opción del compilador, por ejemplo:
CREATE FUNCTION get_userid(username text) RETURNS int
AS $$
#print_strict_params on
DECLARE
userid int;
BEGIN
SELECT users.userid INTO STRICT userid
FROM users WHERE users.username = get_userid.username;
RETURN userid;
END;
$$ LANGUAGE plpgsql;
En caso de fallo, esta función podría producir un mensaje de error como:
ERROR: query returned no rows DETAIL: parameters: username = 'nosuchuser' CONTEXT: PL/pgSQL function get_userid(text) line 6 at SQL statement
La opción STRICT coincide con el comportamiento de
SELECT INTO y las instrucciones relacionadas de Oracle PL/SQL.
A menudo desearás generar comandos dinámicos dentro de tus funciones de
PL/pgSQL, es decir, comandos que involucrarán
diferentes tablas o diferentes tipos de datos cada vez que se ejecuten.
Los intentos normales de PL/pgSQL de almacenar en caché
los planes de los comandos (como se analiza en la Section 41.11.2)
no funcionarán en tales escenarios. Para manejar este tipo de problemas, se proporciona
la instrucción EXECUTE:
EXECUTEcadena_comando[ INTO [STRICT]destino] [ USINGexpresión[, ... ] ];
donde cadena_comando es una expresión que produce una cadena
(de tipo text) que contiene el comando a ejecutar. El destino
opcional es una variable de registro, una variable de fila o una lista de variables simples
y campos de registro/fila separados por comas, en los que se almacenarán los resultados del
comando. Las expresiones opcionales USING suministran valores para ser
insertados en el comando.
No se realiza ninguna sustitución de variables de PL/pgSQL en la cadena de comando calculada. Cualquier valor de variable requerido debe insertarse en la cadena de comando a medida que se construye; o puedes usar parámetros como se describe a continuación.
Además, no hay almacenamiento en caché de planes para los comandos ejecutados a través de
EXECUTE. En su lugar, el comando siempre se planifica cada vez que se ejecuta
la instrucción. De este modo, la cadena de comando se puede crear dinámicamente dentro de la
función para realizar acciones en diferentes tablas y columnas.
La cláusula INTO especifica dónde se deben asignar los resultados de un comando
SQL que devuelve filas. Si se proporciona una variable de fila o una lista de variables, debe
coincidir exactamente con la estructura de los resultados del comando; si se proporciona una
variable de registro, esta se configurará automáticamente para coincidir con la estructura del
resultado. Si se devuelven múltiples filas, solo la primera se asignará a la(s) variable(s)
INTO. Si no se devuelven filas, se asigna NULL a la(s) variable(s)
INTO. Si no se especifica ninguna cláusula INTO, los
resultados del comando se descartan.
Si se proporciona la opción STRICT, se reporta un error a menos que el comando
produzca exactamente una fila.
La cadena de comando puede usar valores de parámetros, a los que se hace referencia en el comando
como $1, $2, etc. Estos símbolos se refieren a los valores
suministrados en la cláusula USING. Este método suele ser preferible a insertar
los valores de los datos en la cadena de comando como texto: evita la sobrecarga en tiempo de
ejecución de convertir los valores a texto y viceversa, y es mucho menos propenso a ataques de
inyección SQL, ya que no es necesario entrecomillar ni escapar. Un ejemplo es:
EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2' INTO c USING checked_user, checked_date;
Ten en cuenta que los símbolos de parámetros solo se pueden usar para valores de datos — si deseas usar nombres de tablas o columnas determinados dinámicamente, debes insertarlos textualmente en la cadena de comando. Por ejemplo, si la consulta anterior debiera realizarse contra una tabla seleccionada dinámicamente, podrías hacerlo así:
EXECUTE 'SELECT count(*) FROM '
|| quote_ident(tabname)
|| ' WHERE inserted_by = $1 AND inserted <= $2'
INTO c
USING checked_user, checked_date;
Un enfoque más limpio es usar la especificación %I de format()
para insertar nombres de tablas o columnas con entrecomillado automático:
EXECUTE format('SELECT count(*) FROM %I '
'WHERE inserted_by = $1 AND inserted <= $2', tabname)
INTO c
USING checked_user, checked_date;
(Este ejemplo se basa en la regla de SQL de que las cadenas literales separadas por una nueva línea se concatenan implícitamente).
Otra restricción de los símbolos de parámetros es que solo funcionan en comandos SQL optimizables
(SELECT, INSERT, UPDATE,
DELETE, MERGE y ciertos comandos que contienen uno de estos).
En otros tipos de instrucciones (genéricamente llamadas instrucciones de utilidad), debes insertar
los valores textualmente, incluso si son solo valores de datos.
Un comando EXECUTE con una cadena de comando constante simple y algunos
parámetros USING, como en el primer ejemplo anterior, es funcionalmente equivalente
a simplemente escribir el comando directamente en PL/pgSQL y permitir
que la sustitución de variables de PL/pgSQL ocurrir automáticamente.
La diferencia importante es que EXECUTE volverá a planificar el comando en cada
ejecución, generando un plan que es específico para los valores de parámetros actuales; mientras que
de otro modo, PL/pgSQL podría crear un plan genérico y almacenarlo en caché
para su reutilización. En situaciones donde el mejor plan depende fuertemente de los valores de los
parámetros, puede ser útil usar EXECUTE para asegurar positivamente que no se
seleccione un plan genérico.
SELECT INTO no es compatible actualmente dentro de EXECUTE;
en su lugar, ejecuta un comando SELECT simple y especifica INTO
como parte del propio comando EXECUTE.
La instrucción EXECUTE de PL/pgSQL no está relacionada con la
instrucción SQL EXECUTE soportada por el servidor
PostgreSQL. La instrucción EXECUTE del servidor no se puede
usar directamente dentro de funciones de PL/pgSQL (y no es necesaria).
Example 41.1. Entrecomillado de valores en consultas dinámicas
Al trabajar con comandos dinámicos, a menudo tendrás que manejar el escape de comillas simples. El método recomendado para entrecomillar texto fijo en el cuerpo de tu función es el entrecomillado de dólar. (Si tienes código heredado que no usa entrecomillado de dólar, consulta la descripción general en la Section 41.12.1, lo que puede ahorrarte algo de esfuerzo al traducir dicho código a un esquema más razonable).
Los valores dinámicos requieren un manejo cuidadoso ya que pueden contener caracteres de comillas.
Un ejemplo utilizando format() (esto asume que estás utilizando entrecomillado de
dólar en el cuerpo de la función, por lo que las comillas no necesitan ser duplicadas):
EXECUTE format('UPDATE tbl SET %I = $1 '
'WHERE key = $2', colname) USING newvalue, keyvalue;
También es posible llamar a las funciones de entrecomillado directamente:
EXECUTE 'UPDATE tbl SET '
|| quote_ident(colname)
|| ' = '
|| quote_literal(newvalue)
|| ' WHERE key = '
|| quote_literal(keyvalue);
Este ejemplo demuestra el uso de las funciones quote_ident y
quote_literal (consulta la Section 9.4). Por seguridad, las
expresiones que contienen identificadores de columnas o tablas deben pasarse por
quote_ident antes de insertarse en una consulta dinámica.
Las expresiones que contienen valores que deberían ser cadenas literales en el comando construido deben
pasarse por quote_literal. Estas funciones toman las medidas adecuadas para devolver
el texto de entrada encerrado en comillas dobles o simples, respectivamente, con cualquier carácter
especial incrustado debidamente escapado.
Debido a que quote_literal está marcada como STRICT, siempre devolverá
null cuando se llame con un argumento null. En el ejemplo anterior, si newvalue o
keyvalue fueran null, toda la cadena de consulta dinámica se convertiría en null, lo
que provocaría un error en EXECUTE. Puedes evitar este problema utilizando la función
quote_nullable, que funciona de la misma manera que quote_literal
excepto que cuando se llama con un argumento null devuelve la cadena NULL. Por ejemplo,
EXECUTE 'UPDATE tbl SET '
|| quote_ident(colname)
|| ' = '
|| quote_nullable(newvalue)
|| ' WHERE key = '
|| quote_nullable(keyvalue);
Si estás tratando con valores que podrían ser nulos, normalmente deberías usar quote_nullable
en lugar de quote_literal.
Como siempre, se debe tener cuidado para asegurar que los valores nulos en una consulta no entreguen
resultados no deseados. Por ejemplo, la cláusula WHERE:
'WHERE key = ' || quote_nullable(keyvalue)
nunca tendrá éxito si keyvalue is null, porque el resultado de usar el operador de
igualdad = con un operando null siempre es null. Si deseas que null funcione como un
valor de clave ordinario, tendrías que reescribir lo anterior como:
'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)
(En la actualidad, IS NOT DISTINCT FROM se maneja de manera mucho menos eficiente que
=, así que no hagas esto a menos que sea necesario. Consulta la
Section 9.2 para obtener más información sobre nulls e IS DISTINCT).
Ten en cuenta que el entrecomillado de dólar solo es útil para entrecomillar texto fijo. Sería una muy mala idea intentar escribir este ejemplo como:
EXECUTE 'UPDATE tbl SET '
|| quote_ident(colname)
|| ' = $$'
|| newvalue
|| '$$ WHERE key = '
|| quote_literal(keyvalue);
porque fallaría si el contenido de newvalue llegara a contener $$.
La misma objeción se aplicaría a cualquier otro delimitador de entrecomillado de dólar que pudieras elegir.
Por lo tanto, para entrecomillar de forma segura texto que no se conoce de antemano, debes
usar quote_literal, quote_nullable o quote_ident,
según corresponda.
Las instrucciones SQL dinámicas también se pueden construir de forma segura utilizando la función
format (consulta la Section 9.4.1). Por ejemplo:
EXECUTE format('UPDATE tbl SET %I = %L '
'WHERE key = %L', colname, newvalue, keyvalue);
%I es equivalente a quote_ident y %L es equivalente
a quote_nullable. La función format se puede utilizar en combinación
con la cláusula USING:
EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
USING newvalue, keyvalue;
Esta forma es mejor porque las variables se manejan en su formato de tipo de datos nativo, en lugar de
convertirlas incondicionalmente a texto y entrecomillarlas mediante %L. También es más
eficiente.
Un ejemplo mucho mayor de un comando dinámico y EXECUTE se puede ver en la
Example 41.10, que construye y ejecuta un comando CREATE FUNCTION
para definir una nueva función.
Existen varias formas de determinar el efecto de un comando. El primer método es utilizar el comando
GET DIAGNOSTICS, que tiene la siguiente forma:
GET [ CURRENT ] DIAGNOSTICSvariable{ = | := }elemento[ , ... ];
Este comando permite recuperar indicadores de estado del sistema. CURRENT es una palabra
de adorno (ruido) (pero consulta también GET STACKED DIAGNOSTICS en la
Section 41.6.8.1). Cada elemento es una palabra
clave que identifica un valor de estado que se asignará a la variable especificada
(que debería ser del tipo de datos adecuado para recibirlo). Los elementos de estado disponibles actualmente
se muestran en la Table 41.1. Se puede utilizar la combinación de dos
puntos e igual (:=) en lugar del token = estándar de SQL. Un ejemplo:
GET DIAGNOSTICS integer_var = ROW_COUNT;
Table 41.1. Elementos de diagnóstico disponibles
| Nombre | Tipo | Descripción |
|---|---|---|
ROW_COUNT | bigint | el número de filas procesadas por el comando SQL más reciente |
PG_CONTEXT | text | línea(s) de texto que describen la pila de llamadas actual (consulta la Section 41.6.9) |
PG_ROUTINE_OID | oid | el OID de la función actual |
El segundo método para determinar los efectos de un comando es verificar la variable especial
llamada FOUND, que es de tipo boolean. FOUND
comienza en false dentro de cada llamada a una función de PL/pgSQL.
Se establece por cada uno de los siguientes tipos de instrucciones:
Una instrucción SELECT INTO establece FOUND en true
si se asigna una fila, o en false si no se devuelve ninguna fila.
Una instrucción PERFORM establece FOUND en true si
produce (and discards) una o más filas, o en false si no se produce ninguna fila.
Las instrucciones UPDATE, INSERT, DELETE
y MERGE establecen FOUND en true si se ve afectada al
menos una fila, o en false si no se ve afectada ninguna fila.
Una instrucción FETCH establece FOUND en true si devuelve
una fila, o en false si no se devuelve ninguna fila.
Una instrucción MOVE establece FOUND en true si reposiciona
con éxito el cursor, o en false en caso contrario.
Una instrucción FOR o FOREACH establece
FOUND en true si itera una o más veces, o en false en caso contrario.
FOUND se establece de esta manera cuando el bucle finaliza; dentro de la
ejecución del bucle, la instrucción del bucle no modifica FOUND, aunque
podría cambiar por la ejecución de otras instrucciones dentro del cuerpo del bucle.
Las instrucciones RETURN QUERY y RETURN QUERY EXECUTE
establecen FOUND en true si la consulta devuelve al menos una fila, o en
false si no se devuelve ninguna.
Otras instrucciones de PL/pgSQL no cambian el estado de FOUND.
Ten en cuenta en particular que EXECUTE cambia la salida de
GET DIAGNOSTICS, pero no cambia FOUND.
FOUND es una variable local dentro de cada función de
PL/pgSQL; cualquier cambio en ella afecta únicamente a la función actual.
A veces resulta útil una instrucción de marcador de posición que no hace nada.
Por ejemplo, puede indicar que una de las ramas de una cadena if/then/else está
vacía deliberadamente. Para este propósito, utiliza la instrucción NULL:
NULL;
Por ejemplo, los siguientes dos fragmentos de código son equivalentes:
BEGIN
y := x / 0;
EXCEPTION
WHEN division_by_zero THEN
NULL; -- ignorar el error
END;
BEGIN
y := x / 0;
EXCEPTION
WHEN division_by_zero THEN -- ignorar el error
END;
Cuál es preferible es una cuestión de gusto.
En PL/SQL de Oracle, no se permiten listas de instrucciones vacías, por lo que se
requieren instrucciones NULL para situaciones
como esta. En su lugar, PL/pgSQL te permite simplemente no escribir nada.