PostgreSQL DISTINCT ON с разными ORDER BY - PullRequest
171 голосов
/ 21 марта 2012

Я хочу выполнить этот запрос:

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

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

PG :: Ошибка: ОШИБКА: выражения SELECT DISTINCT ON должны совпадать с начальным ORDER BYвыражения

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

Ответы [ 7 ]

164 голосов
/ 21 марта 2012

Документация гласит:

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

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

необходимо добавить 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

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

50 голосов
/ 21 марта 2012

Вы можете заказать по 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
36 голосов
/ 21 марта 2012

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.
Связанные с более подробным объяснением:

10 голосов
/ 18 июля 2013

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

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)
4 голосов
/ 25 апреля 2017

Для тех, кто использует 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))
0 голосов
/ 10 мая 2019
SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM purchases
WHERE purchases.product_id = 1
ORDER BY address_id, purchases.purchased_at DESC

ORDER BY address_id , purchase.purchased_at DESC

address_id должен быть добавлен для того, чтобы функция DISTINCT ON ()

0 голосов
/ 23 мая 2017

Вы также можете сделать это, используя group by clause

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