2015-06-17 23:06 GMT-03:00 Luciano Bierhals <[email protected]>:

> Select r1.*
> from registros r1
> join (select max(data) as data, tipo
>           from registro
>          group by tipo) r2 on r1.tipo = r2.tipo and r1.data = r2.data
>

Isso é geralmente conhecido como "groupwise max" e exitem várias formas de
resolver, a sua é uma delas. Não tenho certeza da performance, mas
considerando muitos dados, possivelmente a agregação seria feita com um
HashAggregate, e o join um NestedLoop (talvez?) usando um índice em (tipo,
data). Sendo N = número de "tipos" distintos e M = número de tuplas em
"registros", você teria então: O(M) [para o HashAggregate, que retornaria N
tuplas] + O(NlogM) = O(M + NlogM). -- alguém me corrija se eu estiver
errado, acabei de acordar, o Tico ainda dorme.

A forma que geralmente oferece melhor performance, mas só funciona a partir
da versão 9.3, é usando o LATERAL. Mas, para tanto, você deve ter uma
tabela que referencia "registros" e armazena os "tipos" de forma única,
assumindo que essa tabela exista e se chame "tipos", a consulta seria:

    SELECT t.tipo, r.*
    FROM tipos t, LATERAL(
        SELECT max(r1.data) AS data
        FROM registros r1
        WHERE r1.tipo = t.tipo
    ) r;

Ou, caso queira outras informações do registro (não somente a data), você
pode usar o ORDER BY + LIMIT (ambas devem produzir o mesmo plano de
execução):

    SELECT t.tipo, r.*
    FROM tipos t, LATERAL(
        SELECT r1.*
        FROM registros r1
        WHERE r1.tipo = t.tipo
        ORDER BY r1.data DESC
        LIMIT 1 -- Se quiser padrão SQL (esse é horrível): FETCH FIRST 1
ROW ONLY
    ) r;

De forma sucinta, o LATERAL permite executar uma subconsulta correlacionada
na cláusula FROM, sendo que a execução lógica seria, para cada "tipos"
encontrado execute a subconsulta no LATERAL trazendo a maior data daquele
tipo na tabela "registros". E, isso terá uma boa performance, mas você
precisará *obrigatoriamente* do seguinte índice:

    CREATE INDEX ON registros(tipo, data);

Assim, a consulta será O(NlogM), considerando mesmos N, M usados
anteriormente. Assim, assumindo que tenha muito mais "reigstros" que
"tipos" (o que inferi pelo contexto), essa última abordagem é bem
performática.

Outras abordagens, seriam:

- Usando o DISTINCT ON (específico do PostgreSQL):

    SELECT DISTINCT ON(tipo) *
    FROM registros
    ORDER BY tipo DESC, data DESC;

- Usando uma window function (padrão SQL):

    SELECT *
    FROM (
        SELECT row_number() OVER(PARTITION BY tipo ORDER BY data DESC) AS
rn, *
        FROM registros
    ) t
    WHERE t.rn = 1;

O que nos leva a 4 abordagens diferentes, apenas a DISTINCT ON é específica
do PostgreSQL, as demais todas estão no padrão SQL.

De longe (assumindo a distribuição N << M) a versão com LATERAL é a mais
performática. Existe uma abordagem que "imita" o LATERAL e funciona em
versões anteriores, mas é extremamente complexa e nem um pouco intuitiva,
por isso, qualquer que necessite isto, eu recomendo fortemente usar a
versão 9.3+.

Atenciosamente,
-- 
Matheus de Oliveira
_______________________________________________
pgbr-geral mailing list
[email protected]
https://listas.postgresql.org.br/cgi-bin/mailman/listinfo/pgbr-geral

Responder a