PostgreSQL DISTINCT ON com diferentes ORDER BY

Eu quero executar esta consulta:

SELECT DISTINCT ON (address_id) purchases.address_id, purchases.* FROM purchases WHERE purchases.product_id = 1 ORDER BY purchases.purchased_at DESC 

Mas eu recebo este erro:

PG :: Erro: ERRO: as expressões SELECT DISTINCT ON devem corresponder às expressões ORDER BY iniciais

Adicionando address_id como primeira expressão ORDER BY silencia o erro, mas eu realmente não quero adicionar sorting sobre address_id . É possível fazer sem encomendar por address_id ?

Documentação diz:

DISTINCT ON (expressão [, …]) mantém apenas a primeira linha de cada conjunto de linhas em que as expressões dadas são avaliadas como iguais. […] 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. […] A (s) expressão (ões) DISTINCT ON deve (m) corresponder à (s) expressão (ões) ORDER BY mais à esquerda.

Documentação oficial

Então você terá que adicionar o address_id ao pedido por.

Como alternativa, se você estiver procurando a linha completa que contém o produto comprado mais recente para cada address_id e esse resultado classificado por purchased_at , você está tentando resolver um problema maior de N por grupo que pode ser resolvido pelas seguintes abordagens:

A solução geral que deve funcionar na maioria dos SGBDs:

 SELECT t1.* FROM purchases t1 JOIN ( SELECT address_id, max(purchased_at) max_purchased_at FROM purchases WHERE product_id = 1 GROUP BY address_id ) t2 ON t1.address_id = t2.address_id AND t1.purchased_at = t2.max_purchased_at ORDER BY t1.purchased_at DESC 

Uma solução mais orientada para o PostgreSQL baseada na resposta do @hkf:

 SELECT * FROM ( SELECT DISTINCT ON (address_id) * FROM purchases WHERE product_id = 1 ORDER BY address_id, purchased_at DESC ) t ORDER BY purchased_at DESC 

Problema esclarecido, estendido e resolvido aqui: Selecionando linhas ordenadas por alguma coluna e distintas em outra

Você pode ordenar por address_id em uma subconsulta e, em seguida, ordenar pelo que deseja em uma consulta externa.

 SELECT * FROM (SELECT DISTINCT ON (address_id) purchases.address_id, purchases.* FROM "purchases" WHERE "purchases"."product_id" = 1 ORDER BY address_id DESC ) ORDER BY purchased_at DESC 

Uma subconsulta pode resolvê-lo:

 SELECT * FROM ( SELECT DISTINCT ON (address_id) * FROM purchases WHERE product_id = 1 ) p ORDER BY purchased_at DESC; 

As expressões principais em ORDER BY têm que concordar com colunas em DISTINCT ON , portanto, você não pode ordenar por colunas diferentes no mesmo SELECT .

Use apenas um ORDER BY adicional na subconsulta, se desejar escolher uma linha específica de cada conjunto:

 SELECT * FROM ( SELECT DISTINCT ON (address_id) * FROM purchases WHERE product_id = 1 ORDER BY address_id, purchased_at DESC -- get "latest" row per address_id ) p ORDER BY purchased_at DESC; 

Se purchased_at puder ser NULL , considere DESC NULLS LAST .
Relacionado, com mais explicações:

  • Selecione a primeira linha em cada grupo GROUP BY?
  • PostgreSQL classificar por datetime asc, null primeiro?

A function de janela pode resolver isso em uma passagem:

 SELECT DISTINCT ON (address_id) LAST_VALUE(purchases.address_id) OVER wnd AS address_id FROM "purchases" WHERE "purchases"."product_id" = 1 WINDOW wnd AS ( PARTITION BY address_id ORDER BY purchases.purchased_at DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) 

Para quem usa o Flask-SQLAlchemy, isso funcionou para mim

 from app import db from app.models import Purchases from sqlalchemy.orm import aliased from sqlalchemy import desc stmt = Purchases.query.distinct(Purchases.address_id).subquery('purchases') alias = aliased(Purchases, stmt) distinct = db.session.query(alias) distinct.order_by(desc(alias.purchased_at)) 

Você também pode fazer isso usando a cláusula group by

  SELECT purchases.address_id, purchases.* FROM "purchases" WHERE "purchases"."product_id" = 1 GROUP BY address_id, purchases.purchased_at ORDER purchases.purchased_at DESC