Postgresql 如何使用having子句选择聚合函数旁边的相应记录

Postgresql 如何使用having子句选择聚合函数旁边的相应记录,postgresql,greatest-n-per-group,Postgresql,Greatest N Per Group,假设我有一个带有customer\u id、order\u total和order\u date列的orders表。我想创建一个报告,显示在过去30天内没有下订单的所有客户,并在报告中列出他们上一次订单的总金额 这将获得应出现在报告中的所有客户: select customer, max(order_date), (select order_total from orders o2 where o2.customer = orders.customer order by order_date d

假设我有一个带有customer\u id、order\u total和order\u date列的orders表。我想创建一个报告,显示在过去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

有没有更好的方法不需要子查询,而是使用窗口函数或其他更有效的方法来访问最近订单的总金额?来自的技术是相关的,但是额外的限制似乎阻止了我使用类似DISTINCT ON的东西

尝试将表自身连接起来

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

具有行数窗口功能的解决方案

说明:

在这两种解决方案中,我都使用第一个_值窗口函数。窗口函数的框架由客户定义。客户组中的行按日期降序排列,日期首先给出最新的行。因此,可以获得该订单的最后订单日期和最后订单总数

两种解决方案的区别在于过滤。我展示了这两个版本,因为有时候其中一个速度要快得多

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


但是,如果希望每个组只有一行,则只需确保每个组的预期行被排序为组中的第一行。然后,DISTINCT ON函数可以删除以下所有行。DISTINCT ON customer为每个客户组提供第一个订购行。

我希望这样做时不需要额外的连接,可能需要一个窗口函数。我不认为CTE比我最初的例子更好。你可以在CTE中使用一些东西,比如在id上选择distinct,order\u total from orders。。。而pg应该在第一次出现时停止,这可能会给您带来一些性能改进。使用窗口函数会为查询增加一个额外的周期,可能不会给您带来任何性能改进。不过,如果您需要更多信息,请查看解释结果,选择正确的解决方法应该会有所帮助
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
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
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