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