具有不同 ORDER BY 的 PostgreSQL DISTINCT ON
Posted
技术标签:
【中文标题】具有不同 ORDER BY 的 PostgreSQL DISTINCT ON【英文标题】:PostgreSQL DISTINCT ON with different ORDER BY 【发布时间】:2012-04-05 10:37:54 【问题描述】:我想运行这个查询:
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 表达式必须匹配初始 ORDER BY 表达式
添加address_id
作为第一个ORDER BY
表达式可以消除错误,但我真的不想在address_id
上添加排序。不用address_id
下单可以吗?
【问题讨论】:
您的订单条款已购买_at 而不是address_id。您能否把您的问题说清楚。 我的订单已经购买,因为我想要它,但 postgres 还要求提供地址(请参阅错误消息)。 在这里得到了完整的回答 - ***.com/questions/9796078/… 感谢***.com/users/268273/mosty-mostacho 就我个人而言,我认为要求 DISTINCT ON 来匹配 ORDER BY 是非常值得怀疑的,因为有很多合法的用例可以让它们不同。 postgresql.uservoice 上有一篇文章试图为那些感觉相似的人改变这一点。 postgresql.uservoice.com/forums/21853-general/suggestions/… 遇到了完全相同的问题,并面临同样的限制。目前我已经把它分解成一个子查询然后排序,但是感觉很脏。 【参考方案1】:文档说:
DISTINCT ON (表达式 [, ...] ) 仅保留给定表达式计算结果为相等的每组行的第一行。 [...] 请注意,除非使用 ORDER BY 来确保所需的行首先出现,否则每组的“第一行”是不可预测的。 [...] DISTINCT ON 表达式必须匹配最左边的 ORDER BY 表达式。
Official documentation
因此,您必须将address_id
添加到 order by。
或者,如果您正在寻找包含每个 address_id
的最新购买产品的完整行,并且该结果按 purchased_at
排序,那么您正在尝试解决每组最大 N 个问题,这可以是通过以下方法解决:
应该适用于大多数 DBMS 的通用解决方案:
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
基于@hkf 答案的更面向 PostgreSQL 的解决方案:
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
问题在这里得到澄清、扩展和解决:Selecting rows ordered by some column and distinct on another
【讨论】:
它有效,但顺序错误。这就是为什么我想在 order 子句中去掉 address_id 但是是否有另一种方法可以选择不同地址的最新购买? 如果您需要通过purchased.purchased_at 订购,您可以将purchased_at 添加到您的DISTINCT 条件中:SELECT DISTINCT ON (purchases.purchased_at, address_id)
。但是,具有相同 address_id 但不同 purchase_at 值的两条记录将导致返回集中重复。确保您了解要查询的数据。
问题的精神很明确。无需选择语义。很遗憾,被接受且投票最多的答案并不能帮助您解决问题。
这是一篇 postgresql.uservoice 帖子,试图为那些同意这是一个可疑限制的人解除此限制。 postgresql.uservoice.com/forums/21853-general/suggestions/…【参考方案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
【讨论】:
但这会比一个查询慢,不是吗? 非常勉强。虽然由于您在原始select
中有购买。*,但我认为这不是生产代码?
我要补充一点,对于较新版本的 postgres,您需要为子查询设置别名。例如: SELECT * FROM (SELECT DISTINCT ON (address_id) purchase.address_id, purchase.* FROM "purchases" WHERE "purchases"."product_id" = 1 ORDER BY address_id DESC ) AS tmp ORDER BY tmp.purchased_at DESC
这将返回address_id
两次(不需要)。许多客户都有重复列名的问题。 ORDER BY address_id DESC
毫无意义且具有误导性。它在此查询中没有任何用处。结果是从具有相同address_id
的每组行中任意选择,而不是具有最新purchased_at
的行。模棱两可的问题并没有明确要求,但这几乎可以肯定是 OP 的意图。简而言之:不要使用此查询。我发布了带有解释的替代方案。
为我工作。很好的答案。【参考方案3】:
子查询可以解决:
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 first row in each GROUP BY group? Sort by column ASC, but NULL values first?【讨论】:
如果没有匹配的ORDER BY
,您将无法使用DISTINCT ON
。第一个查询需要在子查询中使用ORDER BY address_id
。
@AristotlePagaltzis:但是你可以。无论你从哪里得到它,它都是不正确的。您可以在同一查询中使用 DISTINCT ON
而不使用 ORDER BY
。在这种情况下,您可以从DISTINCT ON
子句定义的每组对等点中获得任意行。试试看或点击上面的链接了解详细信息和手册链接。 ORDER BY
在同一个查询中(同一个 SELECT
)不能不同意 DISTINCT ON
。我也确实解释过。
嗯,你是对的。我对文档中“除非使用ORDER BY
”注释的含义视而不见,因为对我来说,实现该功能以处理不连续的值集是没有意义的……但不会允许您通过显式排序来利用它。烦人。
@AristotlePagaltzis:这是因为,在内部,Postgres 使用(至少)两种不同算法之一:遍历排序列表或使用哈希值 - 以任何承诺为准快点。在后一种情况下,结果不按DISTINCT ON
表达式排序(还)。
非常感谢!您的第二个查询解决了我的问题并按预期顺序返回结果!【参考方案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)
【讨论】:
如果有人解释查询就好了。 @Gajus:简短说明:它不起作用,只返回不同的address_id
。不过,这个原则可以起作用。相关示例:***.com/a/22064571/939860 或 ***.com/a/11533808/939860。但是对于手头的问题有更短和/或更快的查询。【参考方案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))
【讨论】:
是的,或者更简单,我可以使用:query.distinct(foo).from_self().order(bar)
@LaurentMeyer 你的意思是Purchases.query
?
是的,我的意思是 Purchases.query
这很奇怪,from_self
是不是没有记录?我无法让它工作 - Query
和 QuerySet
在我尝试访问它时引发 AttributeError
哦,等等,这是 SQLAlchemy,不是 Django,我很傻【参考方案6】:
也可以使用以下查询以及其他答案来解决。
WITH purchase_data AS (
SELECT address_id, purchased_at, product_id,
row_number() OVER (PARTITION BY address_id ORDER BY purchased_at DESC) AS row_number
FROM purchases
WHERE product_id = 1)
SELECT address_id, purchased_at, product_id
FROM purchase_data where row_number = 1
【讨论】:
【参考方案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
【讨论】:
这是不正确的(除非purchases
只有两列address_id
和purchased_at
)。由于GROUP BY
,您将需要使用聚合函数来获取未用于分组的每一列的值,因此它们的值都将来自组的不同行,除非您经历了丑陋和低效的体操。这只能通过使用窗口函数而不是 GROUP BY
来解决。以上是关于具有不同 ORDER BY 的 PostgreSQL DISTINCT ON的主要内容,如果未能解决你的问题,请参考以下文章