为啥 group by 被强制在特定列上而不是在其他列上

Posted

技术标签:

【中文标题】为啥 group by 被强制在特定列上而不是在其他列上【英文标题】:Why group by is forced on specific column but not on the others为什么 group by 被强制在特定列上而不是在其他列上 【发布时间】:2020-06-11 20:25:52 【问题描述】:

我有以下查询: (此处的示例架构:https://www.db-fiddle.com/f/5e9gVC6oRidjYwigPWRKm/3)

SELECT 
  t.customer_id
, t.ticket_id
, c.combination_id
, c.possible_prize + c.confirmed_prize + coalesce(cb.bonus_amount,0) AS won_amount
, round(mul(o.value)::numeric,3) AS odds
FROM tickets t
JOIN combinations c ON c.ticket_id = t.ticket_id
LEFT JOIN combination_bonus cb ON cb.combination_id = c.combination_id 
JOIN outcomes o ON o.ticket_id = t.ticket_id AND o.outcome_id = ANY(c.outcomes)

GROUP BY 1,2,3, cb.bonus_amount
ORDER BY 1

没有+ coalesce(cb.bonus_amount,0) 它运行良好,为什么只需要对这个列进行分组,而不是这个等式中的其他两个?

另外,如果我将这一行放入 sum() 中,结果将是完全错误的,因为它会被乘以几次,我不明白为什么以及如何。

非常感谢对这两种情况的解释。

【问题讨论】:

请在每个帖子中提出 1 个(特定研究非重复)问题。请在您的帖子中添加minimal reproducible example 代码,而不仅仅是在链接中。 PS当你得到一个你不期望的结果时,在你隔离第一个返回你不期望的表达式之后,说出你期望的和为什么并说明理由。如果你这样做了,你将不得不参考定义并且可以看到你的期望是错误的。 (在这里,分组依据。)无论如何,请始终阅读文档和 DBMS 手册以了解您正在使用的功能。这些问题是常见问题解答。 一个常见的错误当想要一些连接,每个可能涉及不同的键,一些子查询,每个可能涉及连接和/或聚合,是错误地进行所有连接然后所有聚合或聚合之前聚合。在适当的行上编写单独的聚合和/或聚合一个案例语句选择行;总是加入一个键。有时 DISTINCT 聚合在非键连接后选择正确的值。 (加入 2 个表的非键可以为表的每个键提供多行。)sum data from multiple tables. 【参考方案1】:

没有 + coalesce(cb.bonus_amount,0) 它运行良好,为什么只需要对这一列进行分组,而不是在这个等式中的其他两个?

当涉及列c.possible_prizec.confirmed_prize 时:在group by 子句中不需要这些,因为该子句已经包含c.combination_id(隐藏在位置参数3 后面),即表c 的主键。

Postgres 是正确实现功能相关列概念的罕见数据库之一(它不是唯一的):一旦您将表的主键放在group by 子句中,则无需添加同一张表的其他列:主键唯一标识一个

另一方面,group by 子句中没有表 cb 的主键。你会争辩说你在那个列上精确地带来了带有连接条件的表cb,这在某种程度上保证了唯一性:

LEFT JOIN combination_bonus cb ON cb.combination_id = c.combin

嗯,Postgres 可能没有那么聪明。如果你把它放在那里,你的查询应该可以正常工作,所以:

GROUP BY 1,2,3, cb.combination_id

【讨论】:

是的,postgres 似乎不是那么聪明;)在编辑前阅读您的答案后,我想出了将 cb.combination_id 放入 group by 的相同想法并且它有效。问题的第二部分呢?为什么 sum() 会在那里产生错误的结果? 好的,我解决了。 , c.possible_prize + c.confirmed_prize + coalesce(cb.bonus_amount,0) AS won_amount 等于: , (SELECT sum(c.possible_prize + c.confirmed_prize + coalesce(cb.bonus_amount,0) ) FROM combination c2 LEFT JOIN combination_bonus cb ON cb.combination_id = c.combination_id WHERE c2.combination_id = c.combination_id) AS sum_won_amount 但在子查询中重复所有来自外部查询的 FROM、JOINS 和子句,并将其与 where 子句链接以避免这种情况下的值相乘,这真的是唯一的解决方案吗?【参考方案2】:

评论有点长。

SQL 允许 -- 并且 Postgres 支持 -- 在唯一键或主键上使用 group by,然后在不使用聚合的情况下选择其他列。这称为功能依赖(其他列在功能上依赖于唯一/主键)。

如果您的第一个查询有效,那么它将在 Postgres 中使用此功能——基于 combinations.combination_id 作为主键(或至少是唯一的)。但是,combination_bonusgroup by 中没有密钥。即使combination_bonus.combination_id 主键,Postgres 也可能不够聪明,无法将这些信息用于功能依赖。

因此,只需将整个表达式 coalesce(cb.bonus_amount, 0) 包含在 group by 中。或者使用聚合函数。

【讨论】:

以上是关于为啥 group by 被强制在特定列上而不是在其他列上的主要内容,如果未能解决你的问题,请参考以下文章

在两列上使用 COUNT 和 GROUP BY 的 SQL 查询非常慢

选择描述字段而不将其包含在 GROUP BY 子句中时 GROUP BY id 的最佳方法

为啥 SQL 强制我在 GROUP BY 子句中重复 SELECT 子句中的所有非聚合字段? [关闭]

使用 group_by 时出错,而不是在减去两个日期列 R 时使用排列时出错

如何使用 CASE 语句而不必将其放入 GROUP BY

MySQL GROUP BY 在虚拟列上使用 ORDER BY