sql >> Base de Datos >  >> RDS >> PostgreSQL

Refactorice una función PL/pgSQL para devolver el resultado de varias consultas SELECT

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 (alias float8 )

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