SQL Dinâmico e RETURN tipo
Você deseja executar SQL dinâmico . Em princípio, isso é simples em plpgsql com a ajuda de
EXECUTE . Você não precisa um cursor. Na verdade, na maioria das vezes você fica melhor sem cursores explícitos. O problema que você encontra:você deseja retornar registros de tipo ainda indefinido . Uma função precisa declarar seu tipo de retorno no
RETURNS cláusula (ou com OUT ou INOUT parâmetros). No seu caso, você teria que recorrer a registros anônimos, porque número , nomes e tipos de colunas retornadas variam. Como:CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
No entanto, isso não é particularmente útil. Você precisa fornecer uma lista de definição de coluna com cada chamada. Como:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Mas como você faria isso, se você não conhece as colunas de antemão?
Você poderia usar tipos de dados de documentos menos estruturados, como
json , jsonb , hstore ou xml . Ver:- Como armazenar uma tabela de dados no banco de dados?
Mas, para o propósito desta pergunta, vamos supor que você deseja retornar colunas individuais, digitadas e nomeadas corretamente, tanto quanto possível.
Solução simples com tipo de retorno fixo
A coluna
datahora parece ser um dado, vou assumir o tipo de dados timestamp e que sempre há mais duas colunas com nomes e tipos de dados variados. Nomes abandonaremos em favor de nomes genéricos no tipo de retorno.
Tipos também abandonaremos e converteremos tudo em
text uma vez que todo tipo de dados pode ser convertido em 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$;
As variáveis
_sensors e _type poderia ser parâmetros de entrada em vez disso. Observe a
RETURNS TABLE cláusula. Observe o uso de
RETURN QUERY EXECUTE . Essa é uma das maneiras mais elegantes de retornar linhas de uma consulta dinâmica. Eu uso um nome para o parâmetro da função, apenas para fazer o
USING cláusula de RETURN QUERY EXECUTE menos confuso. $1 na string SQL não se refere ao parâmetro da função, mas ao valor passado com o USING cláusula. (Ambos são $1 em seu respectivo escopo neste exemplo simples.) Observe o valor de exemplo para
_sensors :cada coluna é convertida para digitar text . Esse tipo de código é muito vulnerável à injeção de SQL . Eu uso
quote_ident() para se proteger contra isso. Agrupando alguns nomes de coluna na variável _sensors impede o uso de quote_ident() (e normalmente é uma má ideia!). Certifique-se de que nenhuma coisa ruim possa estar lá de outra maneira, por exemplo, executando individualmente os nomes das colunas por meio de quote_ident() em vez de. Uma VARIADIC parâmetro vem à mente ... Mais simples desde o PostgreSQL 9.1
Com a versão 9.1 ou posterior você pode usar
format() para simplificar ainda mais: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;
Novamente, nomes de colunas individuais poderiam ser escapados corretamente e seriam a maneira mais limpa.
Número variável de colunas que compartilham o mesmo tipo
Depois que sua pergunta for atualizada, parece que seu tipo de retorno foi
- uma variável número de colunas
- mas todas as colunas do mesmo tipo
double precision(aliasfloat8)
Use um
ARRAY digite neste caso para aninhar um número variável de valores. Além disso, retorno um array com nomes de colunas: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$;
Vários tipos de tabelas completas
Para retornar todas as colunas de uma tabela , existe uma solução simples e poderosa usando um 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$;
Ligue (importante!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Substitua
pcdmet na chamada com qualquer outro nome de tabela. Como isso funciona?
anyelement é um tipo de dados pseudo, um tipo polimórfico, um espaço reservado para qualquer tipo de dados não array. Todas as ocorrências de anyelement na função avaliar para o mesmo tipo fornecido em tempo de execução. Ao fornecer um valor de um tipo definido como argumento para a função, definimos implicitamente o tipo de retorno. O PostgreSQL define automaticamente um tipo de linha (um tipo de dado composto) para cada tabela criada, portanto, há um tipo bem definido para cada tabela. Isso inclui tabelas temporárias, o que é conveniente para uso ad hoc.
Qualquer tipo pode ser
NULL . Entregue um NULL valor, convertido para o tipo de tabela:NULL::pcdmet . Agora a função retorna um tipo de linha bem definido e podemos usar
SELECT * FROM data_of() para decompor a linha e obter colunas individuais. pg_typeof(_tbl_type) retorna o nome da tabela como identificador de objeto tipo regtype . Quando convertido automaticamente para text , os identificadores são automaticamente entre aspas duplas e qualificados pelo esquema se necessário, defendendo-se contra injeção de SQL automaticamente. Isso pode até mesmo lidar com nomes de tabela qualificados pelo esquema onde quote_ident() falharia. Ver:- Nome da tabela como parâmetro de função do PostgreSQL