277

我想运行这个查询:

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

4

7 回答 7

271

文档说:

DISTINCT ON ( 表达式 [, ...] ) 仅保留给定表达式计算结果为相等的每组行的第一行。[...] 请注意,除非使用 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

问题在这里得到了澄清、扩展和解决:选择按某列排序的行和另一列不同的行

于 2012-03-20T22:08:32.430 回答
69

您可以在子查询中按 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
于 2012-03-20T22:04:33.793 回答
69

查询可以解决它:

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

相关,有更多解释:

于 2012-03-20T22:40:26.973 回答
11

窗口函数可以一次性解决这个问题:

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)
于 2013-07-18T06:38:46.857 回答
8

对于使用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))
于 2017-04-25T13:41:16.793 回答
0

它也可以使用以下查询以及其他答案来解决。

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
于 2020-11-27T10:15:49.643 回答
-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
于 2017-05-23T04:35:03.663 回答