具有不同 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 - 并匹配您的索引以获得最佳性能。见:

Sort by column ASC, but NULL values first? Why does ORDER BY NULLS LAST affect the query plan on a primary key?

相关,有更多解释:

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 是不是没有记录?我无法让它工作 - QueryQuerySet 在我尝试访问它时引发 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_idpurchased_at)。由于GROUP BY,您将需要使用聚合函数来获取未用于分组的每一列的值,因此它们的值都将来自组的不同行,除非您经历了丑陋和低效的体操。这只能通过使用窗口函数而不是 GROUP BY 来解决。

以上是关于具有不同 ORDER BY 的 PostgreSQL DISTINCT ON的主要内容,如果未能解决你的问题,请参考以下文章

oracle 分页 有无order by情况不同吗

如何在具有现有 order by 的表上使用 OVER(ORDER BY())?

order by 和 group by 的区别

如何从具有重复项的 GROUP BY 中求和不同的值

如何在 django 中包含条件 order_by?

如何使用 UNION 组合两个具有 ORDER BY 的查询?