PostgreSQL DISTINCT ON с разными ORDER BY
Я хочу запустить этот запрос:
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
?
Ответы
Ответ 1
Документация гласит:
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
Проблема уточнена, расширена и решена здесь: Выбор строк, упорядоченных некоторым столбцом и отличающихся друг от друга
Ответ 2
Вы можете заказать по адресу 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
Ответ 3
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
.
Связанный, с большим количеством объяснений:
Ответ 4
Функция окна может решить, что за один проход:
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
Для всех, кто использует 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))
Ответ 6
SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM purchases
WHERE purchases.product_id = 1
ORDER BY **address_id**, purchases.purchased_at DESC
Ответ 7
Вы также можете сделать это, используя предложение 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