PostgreSQL DISTINCT ON与不同的ORDER BY

sl_*_*bug 186 sql postgresql sql-order-by distinct-on

我想运行此查询:

SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM purchases
WHERE purchases.product_id = 1
ORDER BY purchases.purchased_at DESC
Run Code Online (Sandbox Code Playgroud)

但我得到这个错误:

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

添加address_id为第一个ORDER BY表达式会使错误无效,但我真的不想添加排序address_id.是否可以不通过订购address_id

Mos*_*cho 179

文件说:

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
Run Code Online (Sandbox Code Playgroud)

一个更基于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
Run Code Online (Sandbox Code Playgroud)

问题在此处得到澄清,扩展和解决:选择由某些列排序的行和在另一列上排序的行

  • 它有效,但排序错误.这就是为什么我想在订单条款中摆脱address_id (38认同)
  • 问题的精神很明确.无需挑选语义.令人遗憾的是,被接受且投票最多的答案并没有帮助您解决问题. (15认同)
  • 但可能有另一种方法来选择最新的disticnt地址购买? (2认同)
  • 这是一个 postgresql.uservoice 帖子,试图为那些同意这是一个有问题的限制的人解除这个限制。https://postgresql.uservoice.com/forums/21853-general/suggestions/713352-allow-select-distinct-on-a-from-t-order-by-b (2认同)

hkf*_*hkf 54

您可以通过子查询中的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
Run Code Online (Sandbox Code Playgroud)

  • 我想补充一点,对于较新版本的postgres,你需要为子查询添加别名.例如:SELECT*FROM(SELECT DISTINCT ON(address_id)purchases.address_id,purchases.*FROM"purchase"WHERE"purchases"."product_id"= 1 ORDER BY address_id DESC)AS tmp ORDER BY tmp.purchased_at DESC (7认同)
  • 但这会比一个查询慢,不是吗? (2认同)
  • 非常轻微的是.虽然你原来的'select`中有购买.*,但我认为这不是生产代码吗? (2认同)
  • 这将返回 `address_id` 两次(不需要)。许多客户都遇到重复列名的问题。`ORDER BY address_id DESC` 毫无意义且具有误导性。它在此查询中没有任何用处。结果是从具有相同“address_id”的每组行中任意选择,而不是具有最新“purchased_at”的行。这个模棱两可的问题没有明确要求这一点,但这几乎可以肯定是OP的意图。简而言之:***不要使用此查询***。我发布了带有解释的替代方案。 (2认同)

Erw*_*ter 40

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

SELECT *
FROM  (
    SELECT DISTINCT ON (address_id) *
    FROM   purchases
    WHERE  product_id = 1
    ) p
ORDER  BY purchased_at DESC;
Run Code Online (Sandbox Code Playgroud)

领先的表达式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;
Run Code Online (Sandbox Code Playgroud)

如果purchased_at可以NULL,请考虑DESC NULLS LAST.
相关,有更多解释:

  • @AristotlePagaltzis:但你*可以*.无论你从哪里得到它,都是不对的.您可以在同一查询中使用`DISTINCT ON`而不使用`ORDER BY`.在这种情况下,您可以从`DISTINCT ON`子句定义的每组对等体中获得任意行.尝试或按照上面的链接获取详细信息和手册链接.同一个查询中的`ORDER BY`(相同的`SELECT`)只是不能不同意`DISTINCT ON`.我也解释过了. (3认同)
  • 多谢!您的第二个查询解决了我的问题并按预期顺序返回结果! (2认同)

sav*_*kov 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)
Run Code Online (Sandbox Code Playgroud)

  • 如果有人解释了查询,那就太好了. (6认同)
  • @Gajus:简短解释:它不起作用,只返回不同的“address_id”。不过,这个原则“可能”有效。相关示例:/sf/answers/1544520001/ 或 /sf/answers/807366591/。但是对于当前的问题有更短和/或更快的查询。 (2认同)

reu*_*ano 7

对于使用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))
Run Code Online (Sandbox Code Playgroud)

  • 是的,或者更简单,我可以使用:`query.distinct(foo).from_self().order(bar)` (4认同)