SQL dinámico y RETURN
escribir
Desea ejecutar SQL dinámico . En principio, eso es simple en plpgsql con la ayuda de EXECUTE
. No necesitas un cursor De hecho, la mayoría de las veces estará mejor sin cursores explícitos.
El problema con el que te encuentras:deseas devolver registros de un tipo aún no definido . Una función necesita declarar su tipo de retorno en RETURNS
cláusula (o con OUT
o INOUT
parámetros). En su caso, tendría que recurrir a registros anónimos, porque número , nombres y tipos de las columnas devueltas varían. Me gusta:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
Sin embargo, esto no es particularmente útil. Debe proporcionar una lista de definición de columna con cada llamada. Me gusta:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Pero, ¿cómo harías esto si no conoces las columnas de antemano?
Podrías usar tipos de datos de documentos menos estructurados como json
, jsonb
, hstore
o xml
. Ver:
- ¿Cómo almacenar una tabla de datos en la base de datos?
Pero, para el propósito de esta pregunta, supongamos que desea devolver columnas individuales, correctamente escritas y nombradas tanto como sea posible.
Solución simple con tipo de retorno fijo
La columna datahora
parece ser un hecho, asumiré el tipo de datos timestamp
y que siempre hay dos columnas más con diferentes nombres y tipos de datos.
Nombres lo abandonaremos en favor de los nombres genéricos en el tipo de retorno.
Tipos también abandonaremos y enviaremos todo a text
desde cada el tipo de datos se puede convertir a text
.
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, col2 text, col3 text)
LANGUAGE plpgsql AS
$func$
DECLARE
_sensors text := 'col1::text, col2::text'; -- cast each col to text
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE '
SELECT datahora, ' || _sensors || '
FROM ' || quote_ident(_type) || '
WHERE id = $1
ORDER BY datahora'
USING _id;
END
$func$;
Las variables _sensors
y _type
podrían ser parámetros de entrada en su lugar.
Tenga en cuenta la RETURNS TABLE
cláusula.
Tenga en cuenta el uso de RETURN QUERY EXECUTE
. Esa es una de las formas más elegantes de devolver filas de una consulta dinámica.
Uso un nombre para el parámetro de la función, solo para hacer USING
cláusula de RETURN QUERY EXECUTE
menos confuso $1
en la cadena SQL no se refiere al parámetro de función sino al valor pasado con USING
cláusula. (Ambos resultan ser $1
en su ámbito respectivo en este ejemplo simple.)
Tenga en cuenta el valor de ejemplo para _sensors
:cada columna se convierte para escribir text
.
Este tipo de código es muy vulnerable a la inyección SQL . Yo uso quote_ident()
para protegerse contra ella. Agrupando un par de nombres de columnas en la variable _sensors
evita el uso de quote_ident()
(¡y suele ser una mala idea!). Asegúrese de que no haya cosas malas allí de alguna otra manera, por ejemplo, ejecutando individualmente los nombres de las columnas a través de quote_ident()
en cambio. UN VARIADIC
el parámetro viene a la mente ...
Más simple desde PostgreSQL 9.1
Con la versión 9.1 o posterior, puede usar format()
para simplificar aún más:
RETURN QUERY EXECUTE format('
SELECT datahora, %s -- identifier passed as unescaped string
FROM %I -- assuming the name is provided by user
WHERE id = $1
ORDER BY datahora'
,_sensors, _type)
USING _id;
Una vez más, los nombres de las columnas individuales se pueden escapar correctamente y sería la forma más limpia.
Número variable de columnas que comparten el mismo tipo
Después de que su pregunta se actualice, parece que su tipo de devolución tiene
- un número variable de columnas
- pero todas las columnas del mismo tipo
double precision
(aliasfloat8
)
Usa un ARRAY
escriba en este caso para anidar un número variable de valores. Además, devuelvo una matriz con nombres de columna:
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[])
LANGUAGE plpgsql AS
$func$
DECLARE
_sensors text := 'col1, col2, col3'; -- plain list of column names
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE format('
SELECT datahora
, string_to_array($1) -- AS names
, ARRAY[%s] -- AS values
FROM %s
WHERE id = $2
ORDER BY datahora'
, _sensors, _type)
USING _sensors, _id;
END
$func$;
Varios tipos de tablas completas
Para devolver todas las columnas de una tabla , hay una solución simple y poderosa usando un tipo polimórfico :
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT *
FROM %s -- pg_typeof returns regtype, quoted automatically
WHERE id = $1
ORDER BY datahora'
, pg_typeof(_tbl_type))
USING _id;
END
$func$;
Llame (¡importante!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Reemplazar pcdmet
en la llamada con cualquier otro nombre de tabla.
¿Cómo funciona esto?
anyelement
es un pseudo tipo de datos, un tipo polimórfico, un marcador de posición para cualquier tipo de datos que no sea de matriz. Todas las apariciones de anyelement
en la función evalúa al mismo tipo proporcionado en tiempo de ejecución. Al proporcionar un valor de un tipo definido como argumento de la función, definimos implícitamente el tipo de devolución.
PostgreSQL define automáticamente un tipo de fila (un tipo de datos compuesto) para cada tabla creada, por lo que hay un tipo bien definido para cada tabla. Esto incluye tablas temporales, lo cual es conveniente para uso ad-hoc.
Cualquier tipo puede ser NULL
. Entregue un NULL
value, convertido al tipo de tabla:NULL::pcdmet
.
Ahora la función devuelve un tipo de fila bien definido y podemos usar SELECT * FROM data_of()
para descomponer la fila y obtener columnas individuales.
pg_typeof(_tbl_type)
devuelve el nombre de la tabla como tipo de identificador de objeto regtype
. Cuando se convierte automáticamente a text
, los identificadores se entrecomillan automáticamente y se califican según el esquema si es necesario, defenderse contra la inyección de SQL automáticamente. Esto incluso puede tratar con nombres de tablas calificados por esquema donde quote_ident()
fallaría. Ver:
- Nombre de tabla como parámetro de función de PostgreSQL