PostgreSQL DISTINCT ON с разными ORDER BY

123

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

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

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

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

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

  • 0
    Ваше положение о заказе куплено_ не по адресу_адрес. Можете ли вы прояснить свой вопрос.
  • 0
    мой заказ имеет покупку, потому что я хочу его, но postgres также запрашивает адрес (см. сообщение об ошибке).
Показать ещё 1 комментарий
Теги:
sql-order-by
distinct-on

6 ответов

114
Лучший ответ

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

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

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

  • 37
    Это работает, но дает неправильный порядок. Вот почему я хочу избавиться от address_id в предложении заказа
  • 1
    Документация ясна: вы не можете, потому что выбранная строка будет непредсказуемой
Показать ещё 8 комментариев
49

Вы можете заказать по адресу 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
  • 2
    Но это будет медленнее, чем один запрос, не так ли?
  • 2
    Очень незначительно да. Хотя, поскольку у вас есть покупки. * В исходном select , я не думаю, что это рабочий код?
Показать ещё 2 комментария
21

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.
Связанный, с большим количеством объяснений:

  • 0
    Вы не можете использовать DISTINCT ON без соответствующего ORDER BY . Первый запрос требует ORDER BY address_id внутри подзапроса.
  • 3
    @AristotlePagaltzis: Но вы можете . Откуда вы это взяли, это неправильно. Вы можете использовать DISTINCT ON без ORDER BY в том же запросе. В этом случае вы получаете произвольную строку от каждого набора пиров, определенных предложением DISTINCT ON . Попробуйте или перейдите по ссылкам выше для получения подробной информации и ссылок на руководство. ORDER BY в том же запросе (тот же SELECT ) просто не может не согласиться с DISTINCT ON . Я тоже это объяснил.
Показать ещё 2 комментария
10

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

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)
  • 5
    Было бы хорошо, если бы кто-то объяснил запрос.
  • 0
    @Gajus: краткое объяснение: это не работает, только возвращает отличный address_id . Принцип может работать, хотя. Связанные примеры: stackoverflow.com/a/22064571/939860 или stackoverflow.com/a/11533808/939860 . Но есть более короткие и / или более быстрые запросы для решения проблемы.
1

Для всех, кто использует 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))
  • 2
    Да, или даже проще, я смог использовать: query.distinct(foo).from_self().order(bar)
  • 0
    @LaurentMeyer ты имеешь в виду Purchases.query ?
Показать ещё 1 комментарий
-3

Вы также можете сделать это, используя предложение 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
  • 0
    Это неправильно (если purchases не имеет только две колонки address_id и purchased_at ). Из-за GROUP BY вам нужно будет использовать статистическую функцию, чтобы получить значение каждого столбца, не используемого для группировки, поэтому все значения будут поступать из разных строк группы, если вы не пройдете некрасивую и неэффективную гимнастику. Это можно исправить только с помощью оконных функций, а не GROUP BY .

Ещё вопросы

Сообщество Overcoder
Наверх
Меню