如何在具有子句的聚合函数旁边选择相应的记录

Posted

技术标签:

【中文标题】如何在具有子句的聚合函数旁边选择相应的记录【英文标题】:How to select corresponding record alongside aggregate function with having clause 【发布时间】:2018-09-21 21:44:21 【问题描述】:

假设我有一个包含 customer_id、order_total 和 order_date 列的订单表。我想建立一个报告,显示过去 30 天内未下订单的所有客户,并有一列显示他们最后一次订单的总金额。

这会得到所有应该出现在报告中的客户:

select customer, max(order_date), (select order_total from orders o2 where o2.customer = orders.customer order by order_date desc limit 1)
from orders
group by 1
having max(order_date) < NOW() - '30 days'::interval

有没有更好的方法来做到这一点,不需要子查询,而是使用窗口函数或其他更有效的方法来访问最近订单的总金额?来自How to select id with max date group by category in PostgreSQL? 的技术是相关的,但额外的having 限制似乎阻止了我使用DISTINCT ON 之类的东西。

【问题讨论】:

【参考方案1】:

demo:db<>fiddle


row_number窗口函数的解决方案(https://www.postgresql.org/docs/current/static/tutorial-window.html

SELECT 
    customer, order_date, order_total
FROM (
    SELECT
        *, 
        first_value(order_date) OVER w as last_order, 
        first_value(order_total) OVER w as last_total,
        row_number() OVER w as row_count
    FROM orders
    WINDOW w AS (PARTITION BY customer ORDER BY order_date DESC)
) s
WHERE row_count = 1 AND order_date < CURRENT_DATE - 30

DISTINCT ON (https://www.postgresql.org/docs/9.5/static/sql-select.html#SQL-DISTINCT) 的解决方案:

SELECT
    customer, order_date, order_total
FROM (
    SELECT DISTINCT ON (customer)
        *, 
        first_value(order_date) OVER w as last_order, 
        first_value(order_total) OVER w as last_total
    FROM orders
    WINDOW w AS (PARTITION BY customer ORDER BY order_date DESC)
    ORDER BY customer, order_date DESC
) s
WHERE order_date < CURRENT_DATE - 30

说明:

在这两种解决方案中,我都使用first_value 窗口函数。窗口函数的框架由客户定义。客户组中的行按日期降序排列,最新的行在前 (last_value is not working as expected every time)。所以有可能得到这个订单的最后一个order_date和最后一个order_total

两种解决方案的区别在于过滤。我展示了两个版本,因为有时其中一个明显更快

窗口函数样式是在框架内创建行数。每个第一行都可以稍后过滤。这是通过添加row_number 窗口函数来完成的。当您尝试过滤前两个或三个数据集时,此解决方案的好处就显现出来了。您只需将过滤器从 WHERE row_count = 1 更改为 WHERE row_count = 2

但是,如果您只希望每组只有一行,则只需确保每组的预期行被排序为组中的第一行。然后DISTINCT ON 函数可以删除所有后续行。 DISTINCT ON (customer) 给出每个 customer 组的第一(有序)行。

【讨论】:

【参考方案2】:

尝试自己加入表

select o1.customer, max(order_date),
from orders o1
join orders o2 on o1.id=o2.id
group by o1.customer
having max(o1.order_date) < NOW() - '30 days'::interval

select 中的子查询是个坏主意,因为 DB 会为每一行执行一个查询

如果你使用 postgres,你也可以尝试使用 CTE

https://www.postgresql.org/docs/9.6/static/queries-with.html

WITH t as (
select id, order_total from orders o2 where o2.customer = orders.customer 
order by order_date desc limit 1
) select o1.customer, max(order_date),
from orders o1
join t t.id=o2.id
group by o1.customer
having max(order_date) < NOW() - '30 days'::interval

【讨论】:

我想在没有额外连接的情况下执行此操作,可能使用窗口函数。我不认为 CTE 比我原来的例子更好。 您可以在 CTE 中使用 select distinct on (id) id, order_total from orders... 之类的东西,而不是 pg 在第一次出现时停止,这可能会给您带来一些性能改进。使用窗口函数会为查询增加一个额外的循环,并且可能不会给您带来任何性能提升。不过,如果您需要更多信息,查看解释结果,应该有助于选择正确的解决方案

以上是关于如何在具有子句的聚合函数旁边选择相应的记录的主要内容,如果未能解决你的问题,请参考以下文章

选择列表中的列……无效,因为该列没有包含在聚合函数或 GROUP BY 子句中

SQL Server:使用具有相同 OVER 子句的多个聚合/分析函数?

BigQuery 和 GROUP BY 子句

选择列表中的无效表达式(不包含在聚合函数或 GROUP BY 子句中)

SQL Server报错:选择列表中的列无效,因为该列没有包含在聚合函数或 GROUP BY 子句中

HAVING 子句中的多个聚合函数