SUM() 忽略 GROUP BY 并总结 4 行而不是 2

und*_*man 6 mysql group-by sum

GROUP BY在 MySQL中遇到困难。

我的数据库设置:

client_visit
    - id
    - member_id
    - status_type_id (type_of_visit table)
    - visit_starts_at
    - visit_ends_at

member
    - id

schedule_event
    - id
    - member_id
    - starts_at
    - ends_at

type_of_visit
    - id
    - type (TYPE_BOOKED, TYPE_PRESENT etc)
Run Code Online (Sandbox Code Playgroud)

就这个问题而言:a在给定时间member教一门课或领导一项活动 (a schedule_event)。Aclient报名参加此课程或活动。

例如:

客户 A、B 和 C 预订访问,而那些访问client_visitschedule_event_id和组成的表member_id,因此我们知道哪个班级和哪个成员正在教授/或进行活动。

现在,我们想知道给定成员花费在客户注册的教学/领导活动上的总时间(基于client_visit type_of_visit相当于“预订”或“出席”的列)。我们将把成员 ID 82 作为我们的测试用例。

会员 ID 82 在两个不同的班级有 4 个客户,所以如果每个班级花费 2 小时 15 分钟(8100 秒),那么总时间应该是 16200 秒。

首先是我的查询:

SELECT cv.member_id AS `member_id`,
       sch.id AS `scheduleId`,
       cv.visit_starts_at AS `visitStartsAt`,
       TIMESTAMPDIFF(SECOND, sch.starts_at, sch.ends_at) AS `totalTime`
  FROM `schedule_event` AS `sch`
         LEFT JOIN `client_visit` AS `cv` ON cv.schedule_event_id = sch.id
         INNER JOIN `type_of_visit` AS `tov` ON tov.id = cv.status_type_id
 WHERE (tov.type = 'TYPE_BOOKED' OR tov.type = 'TYPE_PRESENT') and cv.member_id = 82
Run Code Online (Sandbox Code Playgroud)

结果如下: 在此处输入图片说明

这向我展示了第一类的客户,以及第二类的客户。我只想要两行,每个班级。所以,我补充一下:

 GROUP BY sch.id
Run Code Online (Sandbox Code Playgroud)

现在,结果如下: 在此处输入图片说明

到现在为止还挺好,

我知道这个成员有两个时间表 ID,所以我修改了组,将它们合并为一个:

 GROUP BY sch.id AND cv.member_id
Run Code Online (Sandbox Code Playgroud)

我希望它会首先根据sch.id(结果已经在上图中显示)和cv.member_id(我们有两行,所以合并后应该是一行)

结果是(我通过添加 GROUP_CONCAT 修改了 scheduleId,所以我们可以看到两个调度 ID 都在那里): 在此处输入图片说明

现在,就像我将两个计划 ID 放在一起一样,我想将两个计划课程的时间相加。

我现在修改查询:

SUM(TIMESTAMPDIFF(SECOND, sch.starts_at, sch.ends_at)) AS `totalTime`
Run Code Online (Sandbox Code Playgroud)

结果是: 在此处输入图片说明 在此处输入图片说明

我有32400!出于某种原因,SUM 仍然可以看到所有 4 行,而不仅仅是唯一的 2 行。

我预计最终的结果是

+-----------+------------+
| member_id | total_time |
+-----------+------------+
| 82        | 16200      |
+-----------+------------+
Run Code Online (Sandbox Code Playgroud)

不需要所有其他列,我只是让它们看看发生了什么

怎么了?

Wil*_*ema 7

我相信您对 GROUP BY 的作用有误解。毫不奇怪,我在第一次学习时遇到了问题,很大程度上是因为 MySQL 手册实际上并没有明确说明 GROUP BY 的作用,至少不是我能找到的(我刚才搜索了很多;很多警告和特殊行为,而不是实际定义)。

我的(即时)定义:

GROUP BY 压缩您的 SELECT 结果,以便为 GROUP BY 子句中指定的列的每个不同的值组合仅返回 1 行。从这个意义上说,它类似于 DISTINCT,但适用于 GROUP BY 中的列而不是 SELECT 语句。

在非 MySQL 领域,您只能选择您在 GROUP BY 子句中指定的列,以及您想要的任何聚合函数。这些聚合函数,包括 SUM,以每行为基础进行操作,仅报告现在“隐藏”的额外行的结果。

如您所见,这就是您的查询实际执行的操作(或者将执行此操作,但我认为您提供了一个不准确的示例,正​​如 ypercube 在评论中指出的那样)。它汇总了所有现在隐藏的额外行,并报告给定的sch.id.

如果您只想要 each 的不同值的总和,则sch.id必须以不同的方式做事才能获得所需的信息。

它不简单的一个原因是 MySQL 不知道您要在总和中包含哪一行。在您的示例 (8100) 中,它们可能完全相同,但不能保证这一点。

由于 MySQL 允许您选择既不在 GROUP BY 子句中指定也不是聚合函数的列,因此它本质上是“随机”选择一个并将其显示给您。虽然实际上不是随机的,但它是不确定的,并且可以随时更改相同的查询和数据,即使在您看来总是给出相同的结果。

因此,在继续之前,您需要决定如何确定每行的哪一行sch.id包含要求和的值。

如果您知道值始终相同,那么一个简单(尽管不一定优化)的解决方案是将原始 GROUP BY 查询包装在另一个查询中(使原始查询成为子查询),然后在外部查询中使用 SUM 函数,没有 GROUP BY 子句。子查询将删除您的重复项,外部查询将总结去重行的总数。


RDF*_*ozz 3

正如威廉·伦泽马(Willem Renzema)所说,你误解了GROUP BY它的工作原理。既然你似乎没有理解他说的话,那么让我试着换一种说法。

GROUP BY从逻辑上讲,它用于将结果集中的行分组在一起。通常,您会提供一个列列表,用于将行分组在一起。GROUP BY sch.id, cv.member_id告诉 SQL 识别这两列的唯一值集,并根据这些值对结果集中的行进行分组。在您的情况下,这两个值有两个唯一值对:

  • cv.member_id= 82, sch.id= 17101
  • cv.member_id= 82, sch.id= 17153

因此,您将得到两组行 - 三组具有第一对值,一组具有第二对值。

向子句添加附加列永远GROUP BY不会导致组数减少 - 要么所有行中的新列都相同(在这种情况下,组数相同),要么新列具有不同的值形式一个或多个小时原始组中的某些行(在这种情况下,您现在将拥有更多组)。

另外(正如威廉所指出的),你有一个语法错误。列表中的列GROUP BY以逗号分隔。在您的 中GROUP BY sch.id AND cv.member_id,您按计算进行分组: ,或将两者sch.id AND cv.member_id视为布尔值的结果。由于两者都不是 0,因此当转换为布尔值时,两者的计算结果均为 1(真),并且组合为真。因此,最终您只得到一组 4 行。sch.idcv.member_id(true AND true)

让我们退后一步,考虑一下您实际上想要做什么(看起来像)。对于给定的member_id,您需要他们参与“已预订”或“当前”类型活动的总时间。

请注意,总时间是从schedule_event表中计算出来的。另请注意,给定的内容可以多次member_id与相同的内容关联。schedule_event因此,为了获得总时间,我们需要识别与schedule_event我们member_id相关的不同行,并对这些唯一值的时间求和。

在这种情况下,最简单的方法是使用子查询来获取schedule_events我们member_id所绑定的不同事件的列表,然后对这些不同事件的总时间求和。

这是一个可以做到这一点的查询:

SELECT `member_id`
      ,SUM(`totalTime`) as `totalTime`
  FROM (
        SELECT DISTINCT
            cv.member_id AS `member_id`,
            sch.id AS `scheduleId`,
            TIMESTAMPDIFF(SECOND, sch.starts_at, sch.ends_at) AS `totalTime`
        FROM 
            `schedule_event` AS `sch`
            INNER JOIN `client_visit` AS `cv` ON cv.schedule_event_id = sch.id
            INNER JOIN `type_of_visit` AS `tov` ON tov.id = cv.status_type_id
        WHERE 
            (tov.type = 'TYPE_BOOKED' OR tov.type = 'TYPE_PRESENT') 
            AND cv.member_id = 82
       ) sq
 GROUP BY `member_id`;
Run Code Online (Sandbox Code Playgroud)

子查询(富有想象力地标记为sq)基本上是您的原始查询。我将您的更改LEFT JOININNER JOIN,因为我们必须有client_visit记录来识别member_id、 和访问类型。但是,我删除SUMtotalTime; 此时,我们只想知道每次schedule_event需要花费的时间。我还补充说DISTINCT- 我们不在乎 thisschedule_event与 this 一起出现多少次member_id;无论出现一次、三次、还是207次,总时间都是一样的。

一旦我们确定了连接到schedule_event的数据member_id,我们就需要所有这些行的总时间schedule_event。因此,我们获取子查询的结果,将它们分组member_id(以防有必要将其拉回多个member_id值),并对每行的计算时间求和schedule_event

由于 joanolo 不厌其烦地为你的问题设置了一个 dbfiddle,所以我接受了他的工作并在最后添加了这个查询,这样你就可以看到结果就是你想要的;更新后的 dbfiddle 链接在这里

我希望这有助于阐明如何GROUP BY真正适合您。