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

Devuelve una tabla dinámica con columnas desconocidas de la función PL/pgSQL

Esto es difícil de resolver, porque SQL exige conocer el tipo de devolución en el momento de la llamada .
Además, una función plpgsql debe tener un tipo de retorno bien definido .

Si elige devolver registros anónimos , obtiene lo que definió:registros anónimos. Postgres no sabe lo que hay dentro. Por lo tanto, se requiere una lista de definiciones de columnas. para descomponer el tipo.

Hay varias soluciones, dependiendo de los requisitos exactos. Si tiene alguna forma de saber el tipo de devolución en el momento de la llamada , sugiero tipos polimórficos como se describe en el último capítulo de esta respuesta ("Varios tipos de tablas completas"):
Refactorice una función PL/pgSQL para devolver el resultado de varias consultas SELECT

Pero eso no cubre agregar otra columna al tipo de retorno en tiempo de ejecución dentro de la función . Eso simplemente no es posible. Yo repensaría todo el enfoque .

En cuanto a su enfoque actual, lo más parecido que se me ocurre sería una tabla temporal (o un cursor), que consulta en una segunda llamada dentro de una transacción única .

Tiene un par de otros problemas en su código . Ver notas a continuación.

Prueba de concepto

CREATE OR REPLACE FUNCTION f_tbl_plus_infowindow (_tbl regclass) -- regclass!
  RETURNS void AS  -- no direct return type
$func$
DECLARE
   -- appending _tmp for temp table
   _tmp text := quote_ident(_tbl::text || '_tmp');
BEGIN

-- Create temp table only for duration of transaction
EXECUTE format(
   'CREATE TEMP TABLE %s ON COMMIT DROP AS TABLE %s LIMIT 0', _tmp, _tbl);

IF EXISTS (
   SELECT 1
   FROM   pg_attribute a
   WHERE  a.attrelid = _tbl
   AND    a.attname  = 'infowindow'
   AND    a.attisdropped = FALSE)
THEN
   EXECUTE format('INSERT INTO %s SELECT * FROM %s', _tmp, _tbl);
ELSE
  -- This is assuming a NOT NULL column named "id"!
   EXECUTE format($x$
      ALTER  TABLE %1$s ADD COLUMN infowindow text;
      INSERT INTO %1$s
      SELECT *, 'ID: ' || id::text
      FROM   %2$s $x$
     ,_tmp, _tbl);
END IF;

END
$func$ LANGUAGE plpgsql;

La llamada tiene que ser en una sola transacción. Es posible que deba iniciar una transacción explícita, según su cliente.

BEGIN;
SELECT f_tbl_plus_infowindow ('tbl');
SELECT * FROM tbl_tmp;  -- do something with the returned rows
ROLLBACK;               -- or COMMIT, does not matter here

Violín SQL.

Alternativamente, puede dejar que la tabla temporal viva durante la sesión. Sin embargo, tenga cuidado con las colisiones de nombres con llamadas repetidas.

Notas

  • Use nombres de parámetros en lugar del ALIAS obsoleto comando.

  • Para "predeterminar" realmente el esquema actual, use la consulta más simple que muestro. Usando regclass hace el truco automáticamente. Detalles:

    • Nombre de tabla como parámetro de función de PostgreSQL

    Además, esto también evita errores de sintaxis y posibles inyecciones SQL de nombres de tablas no estándar (o malformados) en su código original.

  • El código en tu ELSE cláusula no funcionaría en absoluto.

  • TABLE tbl; es básicamente la abreviatura de SELECT * FROM tbl; .

  • Detalles sobre format() en el manual.