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