No PostgreSQL, existe uma função agregada first () segura para o tipo?

21

Reescrita da pergunta completa

Estou procurando uma função agregada First ().

Aqui encontrei algo que quase funciona:

CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

O problema é que, quando uma coluna varchar (n) passa pela primeira função (), é convertida em varchar simples (sem tamanho). Tentando retornar a consulta em uma função como RETURNS SETOF anyelement, recebo o seguinte erro:

ERRO: a estrutura da consulta não corresponde ao tipo de resultado da função Estado: SQL: 42804 Detalhe: Variação de caracteres do tipo retornado não corresponde à variação de caracteres do tipo esperado (40) na coluna 2. Contexto: Função PL / pgSQL vsr_table_at_time (anyelement, timestamp sem fuso horário ) linha 31 em RETURN QUERY

Na mesma página wiki, há um link para uma versão C da função que substituiria a acima. Não sei como instalá-lo, mas me pergunto se esta versão poderia resolver meu problema.

Enquanto isso, existe uma maneira de alterar a função acima para que ela retorne exatamente o mesmo tipo da coluna de entrada?

Alexandre Neto
fonte

Respostas:

17

DISTINCT ON()

Apenas como uma observação, é exatamente DISTINCT ON()isso que faz (não deve ser confundido DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) mantém apenas a primeira linha de cada conjunto de linhas em que as expressões dadas são avaliadas como iguais . As DISTINCT ONexpressões são interpretadas usando as mesmas regras de ORDER BY(veja acima). Observe que a "primeira linha" de cada conjunto é imprevisível, a menos que ORDER BYseja usada para garantir que a linha desejada apareça primeiro. Por exemplo

Então, se você escrever,

SELECT myFirstAgg(z)
FROM foo
GROUP BY x,y;

É efetivamente

SELECT DISTINCT ON(x,y) z
FROM foo;
-- ORDER BY z;

Nisso é preciso o primeiro z. Existem duas diferenças importantes,

  1. Você também pode selecionar outras colunas sem custo adicional de agregação.

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Como não há, GROUP BYvocê não pode usar agregados (reais) com ele.

    CREATE TABLE foo AS
    SELECT * FROM ( VALUES
      (1,2,3),
      (1,2,4),
      (1,2,5)
    ) AS t(x,y,z);
    
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- fails, as you should expect.
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- would not otherwise fail.
    SELECT myFirstAgg(z), sum(z)
    FROM foo
    GROUP BY x,y;

Não esqueça ORDER BY

Além disso, embora eu não tenha ousado, agora vou

Observe que a "primeira linha" de cada conjunto é imprevisível, a menos que ORDER BY seja usado para garantir que a linha desejada apareça primeiro. Por exemplo

Sempre use um ORDER BYcomDISTINCT ON

Usando uma função agregada de conjunto ordenado

Imagino que muitas pessoas estejam procurando first_value, Funções agregadas de conjunto ordenado . Só queria jogar isso lá fora. Seria assim, se a função existisse:

SELECT a, b, first_value() WITHIN GROUP (ORDER BY z)    
FROM foo
GROUP BY a,b;

Mas, infelizmente, você pode fazer isso.

SELECT a, b, percentile_disc(0) WITHIN GROUP (ORDER BY z)   
FROM foo
GROUP BY a,b;
Evan Carroll
fonte
11
O problema com esta resposta é que ela só funciona se você quiser UM agregado em sua lista de seleção, o que não está implícito na pergunta. Se, por exemplo, você desejar selecionar de uma tabela e encontrar vários primeiros valores ordenados, DISTINCT ONnão funcionará neste caso. Não é uma função agregada; na verdade, você está filtrando os dados e pode fazê-lo apenas uma vez.
DB140141
6

Sim, eu descobri uma maneira fácil com o seu caso, usando alguns recursos do PostgreSQL 9.4+

Vamos ver este exemplo:

select  (array_agg(val ORDER BY i))[1] as first_value_orderby_i,
    (array_agg(val ORDER BY i DESC))[1] as last_value_orderby_i,
    (array_agg(val))[1] as last_value_all,
    (array_agg(val))[array_length(array_agg(val),1)] as last_value_all
   FROM (
        SELECT i, random() as val
        FROM generate_series(1,100) s(i)
        ORDER BY random()
    ) tmp_tbl

Espero que ajude você no seu caso.

Mabu Kloesen
fonte
O problema com esta solução é que ela não funciona com DOMAINtipos de dados ou outras pequenas exceções. Também é muito mais complexo e demorado, construindo uma matriz de todo o conjunto de dados. A solução simples seria criar um agregado personalizado, mas até agora ainda não encontrei a solução ideal. Funções da janela também são ruins, uma vez que não pode ser usado da mesma forma que você poderia usar agregados (com instruções de filtro, ou em CROSS JOIN LATERAL)
AlexanderMP
5

Não é uma resposta direta à sua pergunta, mas você deve tentar a first_valuefunção da janela. Funciona assim:

CREATE TABLE test (
    id SERIAL NOT NULL PRIMARY KEY,
    cat TEXT,
    value VARCHAR(2)
    date TIMESTAMP WITH TIME ZONE

);

Então, se você quiser o primeiro item em cada cat(categoria), consultará assim:

SELECT
    cat,
    first_value(date) OVER (PARTITION BY cat ORDER BY date)
FROM
    test;

ou:

SELECT
    cat,
    first_value(date) OVER w
FROM
    test
WINDOW w AS (PARTITION BY cat ORDER BY date);
Ghislain Leveque
fonte
Desculpe, acho que isso não se aplica ao meu caso de uso. First_value não é uma função de agregação, mostrando todos os registros com um determinado valor comum (seu gato de exemplo) que é avaliado como sendo o primeiro de acordo com alguma ordem (sua data de exemplo). Minha necessidade é diferente. Eu preciso, na mesma seleção, agregar várias colunas escolhendo o primeiro valor não nulo. Ou seja, ele deve gerar um único registro para cada combinação de valores em GROUP BY.
Alexandre Neto
2
A descrição acima pode ser feita para o trabalho, jogando distinta na mistura: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Provavelmente ineficiente, mas suficiente para eu continuar com a prototipagem. Definitivamente algo para revisitar embora!
Max Murphy