PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Retornar tabela dinâmica com colunas desconhecidas da função PL/pgSQL


Isso é difícil de resolver, porque o SQL exige saber o tipo de retorno no momento da chamada .
Além disso, uma função plpgsql precisa ter um tipo de retorno bem definido .

Se você optar por retornar registros anônimos , você obtém o que definiu:registros anônimos. Postgres não sabe o que está dentro. Portanto, uma lista de definição de coluna é necessária para decompor o tipo.

Existem várias soluções alternativas, dependendo dos requisitos exatos. Se você tiver alguma maneira de saber o tipo de retorno no momento da chamada , sugiro tipos polimórficos conforme descrito no último capítulo desta resposta ("Vários tipos de tabela completa"):
Refatorar uma função PL/pgSQL para retornar a saída de várias consultas SELECT

Mas isso não cobre a adição de outra coluna ao tipo de retorno em tempo de execução dentro da função . Isso não é possível. Eu repensaria toda a abordagem .

Quanto à sua abordagem atual, a coisa mais próxima que consigo pensar seria uma tabela temporária (ou um cursor), que você consulta em uma segunda chamada dentro de uma transação única .

Você tem alguns outros problemas em seu código . Veja as notas abaixo.

Prova de conceito

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;

A chamada deve ser em uma única transação. Você pode ter que iniciar uma transação explícita, dependendo do seu 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

SQL Fiddle.

Alternativamente, você pode deixar a tabela temporária ativa durante a sessão. No entanto, tenha cuidado com colisões de nomes com chamadas repetidas.

Observações


  • Use nomes de parâmetros em vez do desatualizado ALIAS comando.

  • Para realmente "padrão" para o esquema atual, use a consulta mais simples que eu exponho. Usando regclass faz o truque automaticamente. Detalhes:
    • Nome da tabela como parâmetro de função do PostgreSQL

    Além disso, isso também evita erros de sintaxe e possível injeção de SQL de nomes de tabelas não padrão (ou malformados) em seu código original.

  • O código em seu ELSE cláusula não funcionaria.

  • TABLE tbl; é basicamente a abreviação de SELECT * FROM tbl; .

  • Detalhes sobre format() no manual.