【问题标题】:Select only last value using group by at mysql在 mysql 中使用 group by 仅选择最后一个值
【发布时间】:2013-03-03 12:44:09
【问题描述】:

我有一张表,其中包含有关参加某些活动的数据。我在表中有每次用户发送新考勤的考勤数据,信息是这样的:

mysql> SELECT id_branch_channel, id_member, attendance, timestamp, id_member FROM view_event_attendance WHERE id_event = 782;
+-------------------+-----------+------------+------------+-----------+
| id_branch_channel | id_member | attendance | timestamp  | id_member |
+-------------------+-----------+------------+------------+-----------+
|              1326 |    131327 |        459 | 1363208604 |    131327 |
|              1326 |    131327 |        123 | 1363208504 |    131327 |
|              1326 |    131327 |          1 | 1363208459 |    131327 |
|              1326 |     93086 |          0 |       NULL |     93086 |
|              1326 |     93087 |          0 |       NULL |     93087 |
|              1326 |     93088 |          0 |       NULL |     93088 |
|              1326 |     93093 |          0 |       NULL |     93093 |
|              1326 |     99113 |          0 |       NULL |     99113 |
|              1326 |     99135 |          0 |       NULL |     99135 |
|              1326 |     99199 |          0 |       NULL |     99199 |
|              1326 |     99200 |          0 |       NULL |     99200 |
|              1326 |    131324 |          0 |       NULL |    131324 |
|              1326 |     85850 |          0 |       NULL |     85850 |
|              1326 |     93085 |          0 |       NULL |     93085 |
+-------------------+-----------+------------+------------+-----------+
14 rows in set (0.00 sec)

(这实际上是一个视图,因此某些字段为空)。

我可以按 id_member 分组,因此每个成员只能获得一行(即,只有用户发送的最后一次出席)。但是,当我这样做时,我收到了用户发送的第一个出席信息,而不是最后一个。

mysql> SELECT id_branch_channel, id_member, attendance, timestamp, id_member FROM view_event_attendance WHERE id_event = 782 GROUP BY id_event,id_member;
+-------------------+-----------+------------+------------+-----------+
| id_branch_channel | id_member | attendance | timestamp  | id_member |
+-------------------+-----------+------------+------------+-----------+
|              1326 |    131327 |          1 | 1363208459 |    131327 |
|              1326 |     93086 |          0 |       NULL |     93086 |
|              1326 |    131324 |          0 |       NULL |    131324 |
|              1326 |     93087 |          0 |       NULL |     93087 |
|              1326 |     93088 |          0 |       NULL |     93088 |
|              1326 |     93093 |          0 |       NULL |     93093 |
|              1326 |     99113 |          0 |       NULL |     99113 |
|              1326 |     99135 |          0 |       NULL |     99135 |
|              1326 |     85850 |          0 |       NULL |     85850 |
|              1326 |     99199 |          0 |       NULL |     99199 |
|              1326 |     93085 |          0 |       NULL |     93085 |
|              1326 |     99200 |          0 |       NULL |     99200 |
+-------------------+-----------+------------+------------+-----------+
12 rows in set (0.00 sec)

我已经尝试添加 ORDER BY 子句,但它们根本不起作用...有什么想法吗?

提前致谢!

编辑:这是创建表格的脚本

CREATE OR REPLACE VIEW view_event_attendance 
    AS
        SELECT 
            tbl_event.id_event,
            tbl_member_event.id_member,
            tbl_event.id_branch_channel,
            tbl_member_event_attendance.id_member_event_attendance,
            IF(ISNULL(tbl_member_event_attendance.attendance), 0, tbl_member_event_attendance.attendance) AS attendance,
            tbl_member_event_attendance.timestamp
        FROM 
            tbl_event
            INNER JOIN 
                tbl_member_event ON tbl_member_event.id_event = tbl_event.id_event
                LEFT OUTER JOIN
                    tbl_member_event_attendance ON tbl_member_event_attendance.id_member_event = tbl_member_event.id_member_event
        ORDER BY 
            tbl_member_event_attendance.timestamp DESC;

编辑 2:

非常感谢 MichaelBenjamin,但是使用子查询时的问题是视图的大小:

mysql> DESCRIBE SELECT id_branch_channel, id_member, attendance, timestamp, id_member 
    -> FROM (select * from view_event_attendance order by timestamp desc) as whatever
    -> WHERE id_event = 782 
    -> GROUP BY id_event,id_member;
+----+-------------+-----------------------------+--------+-----------------+-----------------+---------+------------------------------------------------+-------+----------------------------------------------+
| id | select_type | table                       | type   | possible_keys   | key             | key_len | ref                                            | rows  | Extra                                        |
+----+-------------+-----------------------------+--------+-----------------+-----------------+---------+------------------------------------------------+-------+----------------------------------------------+
|  1 | PRIMARY     | <derived2>                  | ALL    | NULL            | NULL            | NULL    | NULL                                           | 16755 | Using where; Using temporary; Using filesort |
|  2 | DERIVED     | tbl_member_event            | index  | id_event        | id_event        | 8       | NULL                                           | 16346 | Using index; Using temporary; Using filesort |
|  2 | DERIVED     | tbl_event                   | eq_ref | PRIMARY         | PRIMARY         | 4       | video_staging.tbl_member_event.id_event        |     1 |                                              |
|  2 | DERIVED     | tbl_member_event_attendance | ref    | id_event_member | id_event_member | 4       | video_staging.tbl_member_event.id_member_event |     1 | Using index                                  |
+----+-------------+-----------------------------+--------+-----------------+-----------------+---------+------------------------------------------------+-------+----------------------------------------------+
4 rows in set (0.08 sec)

如您所见,我的表中有很多行,因此我不想使用子查询...

编辑 3:

但是将 WHERE 添加到子查询中看起来更好......

mysql> DESCRIBE SELECT id_branch_channel, id_member, attendance, timestamp, id_member 
    -> FROM (select * from view_event_attendance where id_event = 782 order by timestamp desc) as whatever
    -> WHERE id_event = 782 
    -> GROUP BY id_event,id_member;
+----+-------------+-----------------------------+-------+-----------------+-----------------+---------+------------------------------------------------+------+----------------------------------------------+
| id | select_type | table                       | type  | possible_keys   | key             | key_len | ref                                            | rows | Extra                                        |
+----+-------------+-----------------------------+-------+-----------------+-----------------+---------+------------------------------------------------+------+----------------------------------------------+
|  1 | PRIMARY     | <derived2>                  | ALL   | NULL            | NULL            | NULL    | NULL                                           |   14 | Using where; Using temporary; Using filesort |
|  2 | DERIVED     | tbl_event                   | const | PRIMARY         | PRIMARY         | 4       |                                                |    1 | Using temporary; Using filesort              |
|  2 | DERIVED     | tbl_member_event            | ref   | id_event        | id_event        | 4       |                                                |   12 | Using index                                  |
|  2 | DERIVED     | tbl_member_event_attendance | ref   | id_event_member | id_event_member | 4       | video_staging.tbl_member_event.id_member_event |    1 | Using index                                  |
+----+-------------+-----------------------------+-------+-----------------+-----------------+---------+------------------------------------------------+------+----------------------------------------------+
4 rows in set (0.01 sec)

如果我找不到其他不使用子查询的东西,我想我会选择这个作为答案......

编辑 4

在看到答案中的 cmets 后,我决定选择另一个作为答案。这是两个查询的 DESCRIBE,我认为最好的解决方案是显而易见的:

mysql> DESCRIBE SELECT 
    ->   id_branch_channel,
    ->   id_member, 
    ->   attendance, 
    ->   timestamp,
    ->   id_member
    -> FROM view_event_attendance AS t1
    -> WHERE id_event = 782
    -> AND timestamp = (SELECT MAX(timestamp)
    ->                  FROM view_event_attendance AS t2 
    ->                  WHERE t1.id_member = t2.id_member 
    ->                    AND t1.id_event = t2.id_event 
    ->                  GROUP BY id_event, id_member)
    -> OR timestamp IS NULL
    -> GROUP BY id_event, id_member;
+----+--------------------+-----------------------------+--------+--------------------+--------------------------+---------+------------------------------------------------+------+-----------------------------------------------------------+
| id | select_type        | table                       | type   | possible_keys      | key                      | key_len | ref                                            | rows | Extra                                                     |
+----+--------------------+-----------------------------+--------+--------------------+--------------------------+---------+------------------------------------------------+------+-----------------------------------------------------------+
|  1 | PRIMARY            | tbl_event                   | index  | PRIMARY            | id_member_branch_channel | 4       | NULL                                           |  208 | Using index; Using temporary; Using filesort              |
|  1 | PRIMARY            | tbl_member_event            | ref    | id_event           | id_event                 | 4       | video_staging.tbl_event.id_event               |   64 | Using index                                               |
|  1 | PRIMARY            | tbl_member_event_attendance | ref    | id_event_member    | id_event_member          | 4       | video_staging.tbl_member_event.id_member_event |    1 | Using where; Using index                                  |
|  2 | DEPENDENT SUBQUERY | tbl_event                   | eq_ref | PRIMARY            | PRIMARY                  | 4       | func                                           |    1 | Using where; Using index; Using temporary; Using filesort |
|  2 | DEPENDENT SUBQUERY | tbl_member_event            | eq_ref | id_event,id_member | id_event                 | 8       | video_staging.tbl_event.id_event,func          |    1 | Using where; Using index                                  |
|  2 | DEPENDENT SUBQUERY | tbl_member_event_attendance | ref    | id_event_member    | id_event_member          | 4       | video_staging.tbl_member_event.id_member_event |    1 | Using where; Using index                                  |
+----+--------------------+-----------------------------+--------+--------------------+--------------------------+---------+------------------------------------------------+------+-----------------------------------------------------------+
6 rows in set (0.00 sec)


mysql> DESCRIBE SELECT *
    -> FROM (SELECT id_branch_channel, id_member, attendance, timestamp, id_event 
    ->       FROM view_event_attendance 
    ->       WHERE id_event = 782 
    ->       ORDER BY timestamp desc
    ->      ) as whatever
    -> GROUP BY id_event,id_member;
+----+-------------+-----------------------------+-------+-----------------+-----------------+---------+------------------------------------------------+------+---------------------------------+
| id | select_type | table                       | type  | possible_keys   | key             | key_len | ref                                            | rows | Extra                           |
+----+-------------+-----------------------------+-------+-----------------+-----------------+---------+------------------------------------------------+------+---------------------------------+
|  1 | PRIMARY     | <derived2>                  | ALL   | NULL            | NULL            | NULL    | NULL                                           |   14 | Using temporary; Using filesort |
|  2 | DERIVED     | tbl_event                   | const | PRIMARY         | PRIMARY         | 4       |                                                |    1 | Using temporary; Using filesort |
|  2 | DERIVED     | tbl_member_event            | ref   | id_event        | id_event        | 4       |                                                |   12 | Using index                     |
|  2 | DERIVED     | tbl_member_event_attendance | ref   | id_event_member | id_event_member | 4       | video_staging.tbl_member_event.id_member_event |    1 | Using index                     |
+----+-------------+-----------------------------+-------+-----------------+-----------------+---------+------------------------------------------------+------+---------------------------------+
4 rows in set (0.00 sec)

【问题讨论】:

  • 我相信您可以通过在其中一个分组列上使用 max() 或类似函数来做您想做的事情。我现在没有设置类似的表来测试。
  • 您可以查看我的答案,了解无需子查询或联接即可工作的解决方案。
  • 我有兴趣查看实际时间;描述的差异是我所期望的,并且不会让我认为一个明显更好。如果您也尝试一下我的答案,那就太好了:)
  • @ysth 我想,但我不知道怎么做...你能澄清一下吗?也许你可以在这里做:sqlfiddle.com/#!2/5a6e7/1

标签: mysql group-by


【解决方案1】:

使用 id_member 的简单组,但选择:

substring(max(concat(from_unixtime(timestamp),attendance)) from 20) as attendance

这会将出勤附加到组中每一行的时间戳,以便能够使用 max() 选择所需的时间戳/出勤,然后仅提取出勤。

concat() 返回的是 19 个字符的格式化时间戳 (YYYY-mm-dd HH:MM:SS),并从字符 20 开始附加出勤率; substring(... from 20) 仅从该组的(字符串方式)最大出席人数中获得出席人数。您可以删除组,只需

select concat(from_unixtime(timestamp),attendance), timestamp, attendance

更好地了解它如何使用 max 来获得正确的出勤率。

【讨论】:

  • 你能用完整的查询更新你的答案吗?稍后我会添加解释。谢谢你的帮助。另外,你能解释一下为什么from 20吗?
  • 我宁愿只提供要选择的表达式,因为您的原始查询不需要其他更改;必须遍历完整的查询会使其他人更难找到并在他们的查询中使用这个想法。
  • 非常聪明,比子查询和连接方法快得多
  • 我喜欢这个解决方案,解决这个问题的速度更快。您必须注意并考虑区分字段的长度,但使用左右很简单。太好了!
【解决方案2】:
SELECT id_branch_channel, id_member, attendance, timestamp, id_member 
FROM (select * from view_event_attendance order by timestamp desc) as whatever
WHERE id_event = 782 
GROUP BY id_event,id_member;

编辑:这可能会产生更好的性能:

SELECT *
FROM (SELECT id_branch_channel, id_member, attendance, timestamp, id_member 
      FROM view_event_attendance 
      WHERE id_event = 782 
      ORDER BY timestamp desc
     ) as whatever
GROUP BY id_event,id_member;

只要结果集可以放入 Innodb_buffer_pool,您就不会看到明显的性能下降。

【讨论】:

  • 从内部查询中仅选择您需要的内容并将 where 移入其中可能会给您带来更好的性能。
【解决方案3】:

我看到JOINSSubquerys 的答案,但我相信一个简单的HAVING 子句应该可以解决问题:

SELECT 
  id_branch_channel,
  id_member, 
  attendance, 
  timestamp,
  id_member
FROM view_event_attendance
WHERE id_event = 782 
GROUP BY id_event, id_member
HAVING MAX(timestamp) OR timestamp IS NULL;

编辑:如果您还想包含这些行,请添加对 IS NULL 的检查。

编辑 2:当您已经将其过滤为 1 个事件时,是否还需要按 id_event 分组?

编辑 3:不知道为什么不赞成,this sql fiddle 表明它有效。

编辑 4: 我必须道歉,@ysth 是正确的,SQL Fiddle 无法正常工作。我配得上-1,但当你投反对票时,至少解释一下原因,这样我也可以自己学习一些东西。

以下工作,但不幸的是,它再次有一个子查询,并且不会比此处发布的其他解决方案更好。

SELECT 
  id_branch_channel,
  id_member, 
  attendance, 
  timestamp,
  id_member
FROM view_event_attendance AS t1
WHERE id_event = 782
AND timestamp = (SELECT MAX(timestamp)
                 FROM view_event_attendance AS t2 
                 WHERE t1.id_member = t2.id_member 
                   AND t1.id_event = t2.id_event 
                 GROUP BY id_event, id_member)
OR timestamp IS NULL
GROUP BY id_event, id_member;

【讨论】:

  • 我明天会测试它,如果它符合我的需要,请告诉你。如果是,那么这将是答案。感谢您的帮助。
  • 这确实有效,我更喜欢这个解决方案。我认为它对数据库的成本更低,我在这方面确实需要改进。非常感谢大家的帮助。我已经知道如何处理子查询,但我不太喜欢它,因此我选择它作为我的问题的解决方案。
  • 你能解释一下它是如何工作的吗? (因为我认为你完全误解了having max(timestamp) 的作用)
【解决方案4】:
SUBSTRING_INDEX(SUBSTRING_INDEX(group_concat(%requiredfield%), ',', count(*)),',',-1)

这将从任何 group_concat 中获取“必填字段”的最后一个值,如果未排序,默认情况下它将是表中的最后一个值。

可以使用 group_concat_ws 来说明可能的空字段。

【讨论】:

    【解决方案5】:

    这是一个选项(未经测试):

    SELECT v.id_branch_channel, v.id_member, v.attendance, v.timestamp, v.id_member 
    FROM view_event_attendance v
        JOIN (
            SELECT id_event, id_member, MAX(attendance) maxattendance
            FROM view_event_attendance 
            GROUP BY id_event, id_member ) m ON 
                v.id_event = m.id_event AND
                v.id_member = m.id_member AND
                v.attendance = m.maxattendance
    WHERE v.id_event = 782 
    GROUP BY v.id_member;
    

    这个概念是获取时间戳的MAX(),并在你的视图上使用该字段来JOIN。您可能不需要所有字段——实际上取决于您的表结构。但这应该会让你朝着正确的方向前进。

    【讨论】:

    • 效果很好!但唯一的问题是只返回一行......我想要我给出的示例中的所有 12 行,但第一个,而不是出勤率为 1 的行,出勤率为 459 的行。感谢您的帮助。我会检查你的代码,看看我是否可以修改它以使我受益(虽然我不想做子查询......)
    • @AbrahamSustaita -- 在不使用子查询的情况下,我能想到的唯一其他选择是在查询中使用用户定义的变量。但是,我认为您不会因此而获得太多的性能提升——事实上,它可能会更慢。我没有完全理解你的问题——将 MAX(timestamp) 替换为 MAX(attendance),你应该得到 459 代替(显然也更新你的 JOIN)......
    • @sgeddes:我相信他想要时间戳最大的那一行的出勤率,即使时间戳较低。
    【解决方案6】:

    执行此操作的一种方法是使用窗口函数和子查询,如果您将条目添加到选择列表中 row_number() over (partition by id_member order by timestamp desc) 这将解析为按时间戳对行排序的数字(其中 1 是最旧的)分组在每个 id_member 组中(如果这没有意义,请运行它,它会很清楚)。然后,您可以从中选择额外列 = 1 的子查询,这将只选择每个组中时间戳最高的行。

    【讨论】:

    • 感谢您的帮助...但我真的不明白您的意思...抱歉。你能给我一个我添加的查询的例子吗?非常感谢您愿意帮助我! :)
    • 窗口函数需要 mysql 8.0 或 mariadb 10.2
    猜你喜欢
    • 1970-01-01
    • 2011-08-06
    • 1970-01-01
    • 2022-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-10
    相关资源
    最近更新 更多