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:
ODISTINCT ON
expressão(ões) deve(m) corresponder aoORDER BY
mais à esquerda expressões). OORDER BY
A cláusula normalmente conterá expressões adicionais que determinam a precedência desejada de linhas dentro de cadaDISTINCT 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 emDISTINCT ON
ouORDER BY
.
-
Você pode inclua qualquer outra expressão noSELECT
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.