PostgreSQL: Passa tabela como argumento na função

11

Estou descobrindo TYPEno PostgreSQL. Eu tenho um TABLE TYPEque alguma tabela deve respeitar (interface). Por exemplo:

CREATE TYPE dataset AS(
    ChannelId INTEGER
   ,GranulityIdIn INTEGER
   ,GranulityId INTEGER
   ,TimeValue TIMESTAMP
   ,FloatValue FLOAT
   ,Status BIGINT
   ,QualityCodeId INTEGER
   ,DataArray FLOAT[]
   ,DataCount BIGINT
   ,Performance FLOAT
   ,StepCount INTEGER
   ,TableRegClass regclass
   ,Tags TEXT[]
   ,WeightedMean FLOAT
   ,MeanData FLOAT
   ,StdData FLOAT
   ,MinData FLOAT
   ,MaxData FLOAT
   ,MedianData FLOAT
   ,Percentiles FLOAT[]
);

Posso criar tabela usando este modelo com:

CREATE TABLE test OF dataset;

Eu já vi muitas opções na API , mas estou um pouco perdido. Gostaria de saber se é possível atribuir esse tipo a INPUT/OUTPUTparâmetros de função .

Digamos que eu tenha FUNCTIONchamado um processque receba uma amostra de registros de um conjunto de dados TABLE source, os processe e depois retorne um TABLE sinkcom o mesmo TYPE.

Ou seja, eu gostaria de saber se é possível criar um TYPEque se comporte assim:

CREATE FUNCTION process(
    input dataset
) RETURNS dataset
AS ...

E isso pode ser chamado assim:

SELECT
    *
FROM
    source, process(input := source) AS sink;

Eu me pergunto que isso é possível com o PostgreSQL e pergunto como fazê-lo. Alguém sabe?


Aqui está um MWE do que estou tentando fazer:

DROP TABLE IF EXISTS source;
DROP FUNCTION IF EXISTS process(dataset);
DROP TYPE dataset;

CREATE TYPE dataset AS (
    id INTEGER
   ,t  TIMESTAMP
   ,x  FLOAT
);


CREATE TABLE source OF dataset;
ALTER TABLE source ADD PRIMARY KEY(Id);
INSERT INTO source VALUES
    (1, '2016-01-01 00:00:00', 10.0)
   ,(2, '2016-01-01 00:30:00', 11.0)
   ,(3, '2016-01-01 01:00:00', 12.0)
   ,(4, '2016-01-01 01:30:00',  9.0)
   ;

CREATE OR REPLACE FUNCTION process(
    _source dataset
)
RETURNS SETOF dataset
AS
$BODY$
SELECT * FROM source;
$BODY$
LANGUAGE SQL;

SELECT * FROM process(source);

Mas não obtém êxito, é como se a fonte fosse percebida como uma coluna em vez de a SETOF RECORDScom o tipo de conjunto de dados.

jlandercy
fonte

Respostas:

13

Seu parâmetro _sourceno MWE adicionado não é mencionado em nenhum lugar. O identificador sourceno corpo da função não possui sublinhado à esquerda e é interpretado como nome constante da tabela independentemente.

Mais importante ainda, não funcionaria assim. O SQL apenas permite parametrizar valores nas instruções DML. Detalhes nesta resposta relacionada:

Solução

Você ainda pode fazê-lo funcionar usando SQL dinâmico com EXECUTEuma função plpgsql. Detalhes:

Ou tente esta pesquisa por perguntas e respostas relacionadas

CREATE TYPE dataset AS (id integer, t timestamp, x float);
CREATE TABLE source OF dataset (PRIMARY KEY(Id));  -- add constraints in same command

INSERT INTO source VALUES
    (1, '2016-01-01 00:00:00', 10.0)
   ,(2, '2016-01-01 00:30:00', 11.0);

CREATE OR REPLACE FUNCTION process(_tbl regclass)
  RETURNS SETOF dataset AS
$func$
BEGIN
RETURN QUERY EXECUTE 'SELECT * FROM ' || _tbl;
END
$func$  LANGUAGE plpgsql;

SELECT * FROM process('source');  -- table name as string literal 

Você pode até fazer isso funcionar para qualquer tabela:

CREATE OR REPLACE FUNCTION process2(_tbl anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE 'SELECT * FROM ' || pg_typeof(_tbl);
END
$func$  LANGUAGE plpgsql;

SELECT * FROM process2(NULL::source);  -- note the call syntax!!

Explicação detalhada:

Erwin Brandstetter
fonte
Obrigado por responder. Vou verificar em algumas horas. Apenas para saber antes do teste, sua solução está aceitando receber linhas de como SELECT. Quero dizer SELECT * FROM process((SELECT * FROM source WHERE cond)).
precisa saber é o seguinte
@j: Não, você passa um nome de tabela . Não há como passar uma tabela em si (sem variável de tabela). Existem várias maneiras de contornar isso. Relacionado: stackoverflow.com/a/27853965/939860 ou stackoverflow.com/a/31167928/939860 . Para trabalhar no resultado de uma consulta que eu usaria um cursor ou uma tabela temporária ...
Erwin Brandstetter
0

Isso fará o que você deseja sem precisar de nenhum SQL dinâmico :

drop table if exists source cascade;
drop function if exists process(dataset) cascade;
drop type if exists dataset cascade;

create type dataset as (
    id integer
   ,t  timestamp
   ,x  float
);

create table source of dataset;
alter table source add primary key(id);
insert into source values
   (1, '2016-01-01 00:00:00', 10.0)
 , (2, '2016-01-01 00:30:00', 11.0)
;

create or replace function process(
    x_source dataset[]
)
returns setof dataset
as
$body$
select * from unnest(x_source);
$body$
language sql;

select *
from
  process(
    array(
      select
        row(id, t, x)::dataset
      from source
    )
  );

Até onde eu sei (depois de pesquisar extensivamente, porque eu tinha o mesmo problema), você não pode passar uma tabela diretamente para uma função.

No entanto, como mostrado, você pode transformar uma tabela em uma matriz []de um tipo personalizado que consiste em vários tipos básicos (semelhantes a uma definição de tabela).

Em seguida, você pode passar esse array e desinvesti-lo novamente em uma tabela quando estiver na função.

Sam Fed
fonte