PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Selecione a primeira linha em cada grupo GROUP BY?


DISTINCT ON é normalmente mais simples e rápido para isso no PostgreSQL .
(Para otimização de desempenho para determinadas cargas de trabalho, veja abaixo.)
SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

Ou mais curto (se não tão claro) com números ordinais de colunas de saída:
SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

Se total pode ser NULL (não prejudicará de qualquer maneira, mas você desejará corresponder aos índices existentes):
...
ORDER  BY customer, total DESC NULLS LAST, id;

Pontos principais


DISTINCT ON é uma extensão do PostgreSQL do padrão (onde apenas DISTINCT em geral SELECT lista é definida).

Liste qualquer número de expressões no DISTINCT ON cláusula, o valor de linha combinado define duplicatas. O manual:

Obviamente, duas linhas são consideradas distintas se diferirem em pelo menos um valor de coluna. Valores nulos são considerados iguais nesta comparação.

Minha ênfase em negrito.

DISTINCT ON pode ser combinado com ORDER BY . Expressões principais em ORDER BY deve estar no conjunto de expressões em DISTINCT ON , mas você pode reorganizar a ordem entre eles livremente. Exemplo.
Você pode adicionar adicionais expressões para ORDER BY para escolher uma linha específica de cada grupo de pares. Ou, como diz o manual:

O DISTINCT ON expressão(ões) deve(m) corresponder ao ORDER BY mais à esquerda expressões). O ORDER BY A cláusula normalmente conterá expressões adicionais que determinam a precedência desejada de linhas dentro de cada DISTINCT ON grupo.

Eu adicionei id como último item para desempate:
"Escolha a linha com o menor id de cada grupo que compartilha o maior total ."

Para ordenar os resultados de uma maneira que discorde da ordem de classificação que determina o primeiro por grupo, você pode aninhar a consulta acima em uma consulta externa com outro ORDER BY . Exemplo.

Se total pode ser NULL, você provavelmente quer a linha com o maior valor não nulo. Adicionar NULLS LAST como demonstrado. Ver:
  • Classificar por coluna ASC, mas primeiro com valores NULL?

O SELECT lista não é restringido por expressões em DISTINCT ON ou ORDER BY de qualquer maneira. (Não é necessário no caso simples acima):

  • Você não precisa inclua qualquer uma das expressões em DISTINCT ON ou ORDER BY .

  • Você pode inclua qualquer outra expressão no SELECT Lista. Isso é fundamental para substituir consultas muito mais complexas por subconsultas e funções de agregação/janela.

Eu testei com as versões 8.3 – 13 do Postgres. Mas o recurso está lá pelo menos desde a versão 7.1, então basicamente sempre.

Índice


O perfeito index para a consulta acima seria um índice de várias colunas abrangendo todas as três colunas na sequência correspondente e com a ordem de classificação correspondente:
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

Pode ser muito especializado. Mas use-o se o desempenho de leitura para a consulta específica for crucial. Se você tiver DESC NULLS LAST na consulta, use o mesmo no índice para que a ordem de classificação corresponda e o índice seja aplicável.

Eficácia/otimização de desempenho


Pese custo e benefício antes de criar índices personalizados para cada consulta. O potencial do índice acima depende em grande parte da distribuição de dados .

O índice é usado porque fornece dados pré-ordenados. No Postgres 9.2 ou posterior, a consulta também pode se beneficiar de uma varredura somente de índice se o índice for menor que a tabela subjacente. O índice tem que ser verificado em sua totalidade, no entanto.

Para poucos linhas por cliente (alta cardinalidade na coluna customer ), isso é muito eficiente. Ainda mais se você precisar de saída classificada de qualquer maneira. O benefício diminui com um número crescente de linhas por cliente.
Idealmente, você tem work_mem suficiente para processar a etapa de classificação envolvida na RAM e não derramar no disco. Mas geralmente definindo work_mem também alta pode ter efeitos adversos. Considere SET LOCAL para consultas excepcionalmente grandes. Descubra quanto você precisa com EXPLAIN ANALYZE . Menção de "Disco: " na etapa de classificação indica a necessidade de mais:
  • Parâmetro de configuração work_mem no PostgreSQL no Linux
  • Otimize a consulta simples usando a data e o texto ORDER BY

Para muitos linhas por cliente (baixa cardinalidade na coluna customer ), uma verificação de índice solto (também conhecido como "skip scan") seria (muito) mais eficiente, mas isso não é implementado até o Postgres 14. (Uma implementação para verificações somente de índice está em desenvolvimento para o Postgres 15. Veja aqui e aqui.)
Para agora, existem técnicas de consulta mais rápidas para substituir isso. Em particular, se você tiver uma tabela separada contendo clientes exclusivos, que é o caso de uso típico. Mas também se você não:
  • SELECT DISTINCT é mais lento do que o esperado na minha tabela no PostgreSQL
  • Otimize a consulta GROUP BY para recuperar a última linha por usuário
  • Otimizar a consulta máxima em grupo
  • Consulte as últimas N linhas relacionadas por linha

Referências


Veja a resposta separada.