【问题标题】:How to access truncated value in GROUP BY from inner query?如何从内部查询访问 GROUP BY 中的截断值?
【发布时间】:2021-03-05 20:18:10
【问题描述】:

我遇到了一个有趣的情况,内部查询无法访问GROUP BY 子句中使用的截断值。如何从父查询访问 trunc-modified GROUP BY 子句?

这是一个精简版:

SELECT
date_trunc('week', mainEvent.timestamp::date + 1)::date -1 AS weekly,
   (
        SELECT sum(p.value)
        FROM myschema.purchase as p
        WHERE p.non_unique_id = mainEvent.non_unique_id
              AND date_trunc('week', p.timestamp::date + 1)::date -1 
                = date_trunc('week', mainEvent.timestamp::date + 1)
        GROUP BY (date_trunc('week', p.timestamp::date + 1)::date -1)::date
   ) as percent_of_week
FROM myschema.event as mainEvent
WHERE mainEvent.internal_feed_name IS NOT NULL
GROUP BY weekly, mainEvent.non_unique_id;

这会产生错误subquery uses ungrouped column "mainevent.timestamp" from outer query Position: 1587

我尝试将外部 GROUP BY 子句更改为:

GROUP BY date_trunc('week', mainEvent.timestamp::date + 1)::date -1, mainEvent.non_unique_id

但它返回相同的错误。但是,如果我从外部 GROUP BY 中删除 trunc

GROUP BY mainEvent.timestamp

引用外部查询的GROUP BY“有效”。这让我认为这原则上有效,但 PostGres 不理解变异的分组。也许有一些语法糖可以使这项工作?有没有办法通过创建一个临时表来做到这一点?我还尝试了窗口函数sum(over) 等的一些变体,但我仍然遇到引用父分组列的问题...我不想按原始时间戳分组,我想每周分组。

目标:我需要在我做伪代码的地方汇总数据:(x / sum(x) in week) as percent_of_week 每周总计的总和。

所以一些数学除以组的总和。实际上,子查询有一个与每个唯一相关的分子(为简洁起见,此处省略),除以主组week 的总和,这就是为什么简单地加入不起作用的原因。我的架构中也没有外键(原始分析数据)。

【问题讨论】:

  • 嗨,有趣,也许用 cte 会更容易? postgresqltutorial.com/postgresql-cte
  • 我用GROUP BY 进行子查询几乎从来都不是人们想要的——因为它可以返回多行并产生错误。样本数据、期望的结果以及对逻辑的清晰解释会有所帮助。
  • @GordonLinoff 好吧,如果是这种情况,我会收到运行前错误subquery returns multiple rows。情况并非如此,当我使用非截断分组时,我得到了预期的 1 行计数。我在其他地方使用这种模式,子查询可以访问父GROUP BY
  • 如果将子查询中的date_trunc('week', mainEvent.timestamp::date + 1) 替换为weekly::date +1 会怎样?或者只是weekly 并将比较的另一边设为-2 而不是-1?这可能使数据库能够识别出被比较的值取决于GROUP BY 列而不是基础列。
  • @Fred 我试过了,但是别名weekly 不适用于内部查询。

标签: sql postgresql window-functions


【解决方案1】:

多个问题。考虑LEFT JOIN LATERAL 而不是:

SELECT *
FROM  (
   SELECT non_unique_id
        , date_trunc('week', timestamp + interval '1 day')::date - 1 AS weekly
   FROM   myschema.event
   WHERE  internal_feed_name IS NOT NULL
   GROUP  BY 1, 2
   ) main_event
LEFT   JOIN LATERAL (
   SELECT sum(p.value) AS sum_value
   FROM   myschema.purchase p
   WHERE  p.non_unique_id = main_event.non_unique_id
   AND    p.timestamp >= main_event.weekly
   AND    p.timestamp <  main_event.weekly + 7
   ) p ON true;

在您的原文中,相关子查询引用了输入mainEvent.timestamp未分组,因为这在逻辑上发生在聚合之前。这就是您报告错误消息的直接原因。

您无法通过引用 输出 列名称 weekly 来解决此问题,因为它在相关子查询中不可见。您必须重写查询:首先聚合,然后加入表purchase

我建议使用更简洁的LEFT JOIN LATERAL 而不是相关的子查询(尽管现在可以在下一个查询级别这样做)。

不需要在子查询中添加另一个GROUP BY,因为它应该在任何情况下都生成恰好一个 行。只需将其删除。

我通过使用位置引用而不是重复SELECT 列表中的表达式来缩短代码。这是可选的。见:

使用“sargable”表达式通常效率更高:

WHERE    p.timestamp >= main_event.weekly
AND      p.timestamp <  main_event.weekly + 7

假设您想要从weekly 开始的那一周。否则相应地移动两个边界。

关于LATERAL

【讨论】:

  • 哇,这太棒了。你能告诉我我是一个通常不做 SQL 的应用工程师,现在做分析吗?哈哈。好吧,我希望这是 SARGable,但时间戳没有被索引,并且由于上游系统,我无法更改模式。超级有用,除非您已经知道要查找的内容,否则搜索此类内容会出现很多不相关的结果。
  • 我发现这个描述的方式可以帮助我理解横向的作用,它就像一个 for each 循环。
  • @FlavorScape:purchase.timestamp 上的索引对于这个查询有很大帮助(如果表很大并且选择很小)。但是即使没有索引,“sargable”谓词通常也比它们丑陋的兄弟姐妹更快。它们不需要为每一行计算一个表达式,只需简单的比较。
  • 横向连接救了我的命!我能够在 2 小时内重新编写我上周所做的所有工作的更清晰的查询!此外,数据来自第 3 方处理器,如果我进行架构更改,它们会中断......我从 CS 的角度知道索引和分区如何使这个查询变得如此之快!希望我能...
猜你喜欢
  • 1970-01-01
  • 2017-12-30
  • 1970-01-01
  • 2017-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多