PostgreSQL使用不同的ORDER BY打开


216

我想运行此查询:

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

但是我得到这个错误:

PG ::错误:错误:SELECT DISTINCT ON表达式必须匹配初始ORDER BY表达式

添加address_id为第一个ORDER BY表达式可以使错误消失,但是我真的不想添加sorting over address_id。是否可以不按顺序进行address_id


您的订单子句中的purchase_at不是address_id。您能否清楚说明问题。
2012年

我的订单已经购买,因为我想要它,但是postgres也要求提供地址(请参阅错误消息)。
sl_bug 2012年


我个人认为,要求DISTINCT ON来匹配ORDER BY是非常有问题的,因为有很多合法的用例可以使它们有所不同。在postgresql.uservoice上有一篇文章试图为那些有类似感觉的人更改此设置。postgresql.uservoice.com/forums/21853-general/suggestions/…–
分号

遇到了完全一样的问题,面临着同样的挑战。目前,我已将其分解为一个子查询,然后进行排序,但感觉很脏。
Guy Park

Answers:


207

文档说:

DISTINCT ON(expression [,...])仅保留给定表达式求值相等的每组行的第一行。[...]注意,除非使用ORDER BY来确保所需的行首先出现,否则每个集合的“第一行”都是不可预测的。[...] DISTINCT ON表达式必须与最左边的ORDER BY表达式匹配。

官方文件

因此,您必须添加 address_id至。

另外,如果您要查找包含每个产品的最新购买商品的完整行,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

在此阐明,扩展和解决的问题:选择按某列排序且在另一列上不同的行


40
它有效,但是给出了错误的排序。这就是为什么我要在order子句中删除address_id的原因
sl_bug 2012年

1
文档很明确:您不能这样做,因为所选行将不可预测
Mosty Mostacho 2012年

3
但是,也许还有另一种方式选择最新购买的不同地址吗?
sl_bug 2012年

1
如果您需要通过purchases.purchased_at进行订购,则可以将Purchased_at添加到您的DISTINCT条件中:SELECT DISTINCT ON (purchases.purchased_at, address_id)。但是,两个具有相同address_id但purchase_at值不同的记录将导致返回集中重复。确保您对所查询的数据有所了解。
布伦丹·本森

23
问题的精神很明确。无需选择语义。令人遗憾的是,被接受并获得最多投票的答案无法帮助您解决问题。
nicooga

55

您可以在子查询中按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
但是,这将比仅一个查询慢,不是吗?
sl_bug 2012年

2
是的。尽管您原本有购买商品* select,但我认为这不是生产代码吗?
HKF 2012年

8
我要补充一点,对于较新版本的postgres,您需要为子查询加上别名。例如:SELECT * FROM(SELECT DISTINCT ON(address_id)purchases.address_id,采购。* FROM“ purchases” WHERE“ purchases”。“ product_id” = 1 ORDER BY address_id DESC)AS tmp ORDER BY tmp.purchased_at DESC
aembke

这将返回address_id两次(不需要)。许多客户端在重复的列名上遇到问题。ORDER BY address_id DESC是毫无意义和误导的。在此查询中没有任何用处。结果是从具有相同行的每组行中任意选择address_id,而不是具有最新行的行purchased_at。模棱两可的问题并没有明确要求,但这几乎是OP的意图。简而言之:不要使用此查询。我发布了带有解释的替代方案。
Erwin Brandstetter

为我工作。好答案。
Matt West

46

一个子查询可以解决这个问题:

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。但是,如果要使用它,请确保匹配您的索引。看到:

相关,更多说明:


DISTINCT ON没有匹配项就无法使用ORDER BY。第一个查询需要一个ORDER BY address_id内部子查询。
亚里斯多德·帕加尔兹

4
@AristotlePagaltzis:但是可以。无论您从哪里得到的,都是不正确的。您可以在同一查询中使用DISTINCT ON不使用ORDER BYDISTINCT ON在这种情况下,您将从子句定义的每个对等体集合中获得任意行。试试看或按上面的链接获取详细信息和手册链接。ORDER BY在同一查询(相同SELECT)中,不能不同意DISTINCT ON。我也做了解释。
Erwin Brandstetter

嗯,是的。我ORDER BY对文档中“不可预测的除非使用”注释的含义视而不见,因为对我来说,该功能实现为能够处理非连续的值集并不有意义……但不允许您使用以明确的顺序利用它。烦死了
亚里斯多德·帕加尔兹

@AristotlePagaltzis:这是因为,在内部,Postgres使用(至少)两种不同的算法之一:遍历排序列表或使用哈希值-两者中的任何一种都会更快。在后一种情况下,结果尚未按DISTINCT ON表达式排序(尚未)。
Erwin Brandstetter

2
谢谢。您的答案总是清晰而有用的!
Andrey Deineko

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)

7
如果有人解释了查询,那就太好了。
朱斯(Gajus)'17

@Gajus:简短的解释:它不起作用,仅返回distinct address_id。但是,该原理可能有效。相关示例:stackoverflow.com/a/22064571/939860stackoverflow.com/a/11533808/939860。但是对于眼前的问题有更短和/或更快速的查询。
Erwin Brandstetter

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))

2
是的,甚至更简单,我能够使用:query.distinct(foo).from_self().order(bar)
Laurent Meyer

@LaurentMeyer是什么意思Purchases.query
reubano

是的,我的意思是Purchases.query
Laurent Meyer

-2

您也可以使用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
亚里斯多德·帕加尔兹
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.