110

我希望能够从电子邮件表中选择一堆行,并按发件人对它们进行分组。我的查询如下所示:

SELECT 
    `timestamp`, `fromEmail`, `subject`
FROM `incomingEmails` 
GROUP BY LOWER(`fromEmail`) 
ORDER BY `timestamp` DESC

该查询几乎可以按我的意愿工作——它选择按电子邮件分组的记录。问题是主题和时间戳与特定电子邮件地址的最新记录不对应。

例如,它可能会返回:

fromEmail: john@example.com, subject: hello
fromEmail: mark@example.com, subject: welcome

当数据库中的记录是:

fromEmail: john@example.com, subject: hello
fromEmail: john@example.com, subject: programming question
fromEmail: mark@example.com, subject: welcome

如果“编程问题”主题是最新的,我如何让 MySQL 在对电子邮件进行分组时选择该记录?

4

6 回答 6

149

一个简单的解决方案是先使用 ORDER 语句将查询包装到子选择中,然后应用 GROUP BY :

SELECT * FROM ( 
    SELECT `timestamp`, `fromEmail`, `subject`
    FROM `incomingEmails` 
    ORDER BY `timestamp` DESC
) AS tmp_table GROUP BY LOWER(`fromEmail`)

这类似于使用连接,但看起来要好得多。

在带有 GROUP BY 子句的 SELECT 中使用非聚合列是非标准的。MySQL 通常会返回它找到的第一行的值并丢弃其余的值。任何 ORDER BY 子句仅适用于返回的列值,而不适用于丢弃的列值。

重要更新 选择用于在实践中工作但不应依赖的非聚合列。根据MySQL 文档, “这主要是在每个未在 GROUP BY 中命名的非聚合列中的所有值对于每个组都相同时很有用。服务器可以自由地从每个组中选择任何值,因此除非它们相同,否则这些值选择是不确定的。”

5.7.5开始,默认启用 ONLY_FULL_GROUP_BY,因此非聚合列会导致查询错误 (ER_WRONG_FIELD_WITH_GROUP)

正如@mikep 在下面指出的那样,解决方案是使用5.7 及更高版本的ANY_VALUE()

http://www.cafewebmaster.com/mysql-order-sort-group https://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html https://dev.mysql .com/doc/refman/5.7/en/group-by-handling.html https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_any-value

于 2012-03-21T00:45:45.777 回答
47

正如已经在回复中指出的那样,当前答案是错误的,因为 GROUP BY 从窗口中任意选择记录。

如果使用 MySQL 5.6 或 MySQL 5.7 with ONLY_FULL_GROUP_BY,则正确的(确定性)查询是:

SELECT incomingEmails.*
  FROM (
    SELECT fromEmail, MAX(timestamp) `timestamp`
    FROM incomingEmails
    GROUP BY fromEmail
  ) filtered_incomingEmails
  JOIN incomingEmails USING (fromEmail, timestamp)
GROUP BY fromEmail, timestamp

为了使查询有效地运行,需要适当的索引。

请注意,为了简化起见,我删除了LOWER()在大多数情况下不会使用的 。

于 2016-02-17T12:00:28.350 回答
44

这是一种方法:

SELECT cur.textID, cur.fromEmail, cur.subject, 
     cur.timestamp, cur.read
FROM incomingEmails cur
LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.timestamp < next.timestamp
WHERE next.timestamp is null
and cur.toUserID = '$userID' 
ORDER BY LOWER(cur.fromEmail)

基本上,你加入表格本身,搜索后面的行。在 where 子句中,您声明不能有后面的行。这只会为您提供最新的行。

如果可以有多个电子邮件具有相同的时间戳,则此查询需要改进。如果电子邮件表中有增量 ID 列,请将 JOIN 更改为:

LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.id < next.id
于 2009-06-30T22:59:22.747 回答
29

在 ORDER BY 之后执行 GROUP BY,方法是使用 GROUP BY 包装查询,如下所示:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from
于 2013-04-30T19:53:32.223 回答
22

根据 SQL 标准,您不能在选择列表中使用非聚合列。MySQL 允许这种用法(除非使用 ONLY_FULL_GROUP_BY 模式),但结果是不可预测的。

ONLY_FULL_GROUP_BY

您应该首先选择 fromEmail、MIN(read),然后使用第二个查询(或子查询) - 主题。

于 2009-06-30T22:59:27.427 回答
4

对于比显示的更复杂的查询,我在这两种方法中都遇到了困难,因为无论我放置什么索引,子查询方法都非常低效,而且我无法通过 Hibernate 获得外部自联接

最好(也是最简单)的方法是按构造为包含所需字段的串联的内容进行分组,然后使用 SELECT 子句中的表达式将它们拉出。如果您需要执行 MAX(),请确保您想要 MAX() 覆盖的字段始终位于连接实体的最重要端。

理解这一点的关键是,只有当这些其他字段对于满足 Max() 的任何实体都是不变的时,查询才有意义,因此就排序而言,可以忽略串联的其他部分。它在此链接的最底部解释了如何执行此操作。http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

如果您可以获得插入/更新事件(如触发器)来预先计算字段的串联,您可以对其进行索引,并且查询将与分组依据一样快于您实际想要 MAX 的字段( )。您甚至可以使用它来获得多个字段的最大值。我用它来对表示为嵌套集的多维树进行查询。

于 2012-10-31T14:00:56.690 回答