PostgreSQL DISTINCT ON с разным порядком по

Я хочу запустить этот запрос:

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

но я получаю эту ошибку:

PG:: Error: ERROR: SELECT DISTINCT on выражения должны соответствовать начальному порядку по выражениям

добавлять address_id первым ORDER BY выражение отключает ошибку, но я действительно не хочу добавлять сортировку по address_id. Можно ли обойтись без заказа address_id?

6 ответов


документация говорит:

DISTINCT ON (выражение [,...]) сохраняет только первую строку каждого набора строк, где заданные выражения оцениваются равными. [...] Обратите внимание, что" первая строка " каждого набора непредсказуема, если не используется ORDER BY, чтобы гарантировать, что нужная строка появится первой. [...] Выражение(ы) DISTINCT ON должно соответствовать самому левому порядку по выражению(ам).

официальная документация

так ты нужно добавить address_id для заказа.

альтернативно, если вы ищете полную строку, содержащую самый последний купленный продукт для каждого address_id и этот результат отсортирован по purchased_at тогда вы пытаетесь решить наибольшую проблему N в группе, которая может быть решена с помощью следующих подходов:

общее решение, которое должно работать в большинстве СУБД:

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

более PostgreSQL-ориентированное решение на основе @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

проблема прояснена, расширена и решена здесь:выбор строк, упорядоченных по одному столбцу и отличных от другого


вы можете заказать по address_id в подзапросе, а затем заказать по тому, что вы хотите во внешнем запросе.

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

A подзапрос может решить это:

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

ведущий выражения в ORDER BY должны согласиться с колонками в DISTINCT ON, поэтому вы не можете заказать по разным столбцам в одном и том же SELECT.

используйте только дополнительный ORDER BY в подзапросе, если вы хотите выбрать определенную строку из каждого набора:

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;

если purchased_at может быть NULL, считать DESC NULLS LAST.
Связанный, с более подробным объяснением:


функция окна может решить это за один проход:

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)

для тех, кто использует Flask-SQLAlchemy, это сработало для меня

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))

вы также можете сделать это, используя предложение 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