MySQL的“分组依据”和“排序依据”


96

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

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选择该记录?

Answers:


140

一个简单的解决方案是将查询包裹与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


7
几年前,我提出了相同的解决方案,这是一个很好的解决方案。向b7kich致敬。不过这里有两个问题... GROUP BY不区分大小写,因此不需要LOWER(),其次,$ userID似乎是直接来自PHP的变量,如果$ userID由用户提供而不是强制执行,则您的代码可能受到sql注入的攻击为整数。
velcrow 2013年

最重要的更新也适用于MariaDB的:mariadb.com/kb/en/mariadb/...
亚瑟Shipkowski

1
As of 5.7.5 ONLY_FULL_GROUP_BY is enabled by default, i.e. it's impossible to use non-aggregate columns.可以在运行时更改SQL模式,而无需管理员权限,因此禁用ONLY_FULL_GROUP_BY非常容易。例如:SET SESSION sql_mode = '';。演示:db-fiddle.com/f/esww483qFQXbXzJmkHZ8VT/3
mikep

1
或者,绕过启用的ONLY_FULL_GROUP_BY的另一种方法是使用ANY_VALUE()。查看更多dev.mysql.com/doc/refman/8.0/en/...
mikep

42

这是一种方法:

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

说的textID是模棱两可= /
John Kurlak 09年

1
然后删除歧义词,并在其前面加上表名,例如cur.textID。答案也改变了。
2009年

这是使用Doctrine DQL可能的唯一解决方案。
VisioN

当您试图对多个列进行自我联接时,这种方法不起作用。IE,当您尝试查找最新的电子邮件和最新的用户名,并且需要多个自左联接才能在单个查询中执行此操作。
Loveen Dyall

当与过去和未来的时间戳/日期,工作限制结果集非将来的日期,则需要另一个条件添加到LEFT JOIN标准AND next.timestamp <= UNIX_TIMESTAMP()
fyrye

32

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

如果使用MySQL 5.6或MySQL 5.7和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(),在大多数情况下不会使用。


2
这应该是正确的答案。我刚刚在我的网站上发现了与此相关的错误。将order by在其他的答案子选择,具有完全没有效果。
杰特(Jette)

1
天哪,请将此作为可接受的答案。被接受的人浪费了我5个小时的时间:(
理查德·凯西

29

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

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from

1
因此,GROUP BY`自动选择最新的time,或最新的time还是随机的?
xrDDDD

1
之所以选择最新时间,是因为我们正在排序time DESC,然后分组依据采用第一个(最新)。
11101101b 2013年

现在,如果我可以在mysql 5.1的VIEWS中对子选择执行JOINS操作。也许该功能出现在更新的版本中。
IcarusNM 2015年

21

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

ONLY_FULL_GROUP_BY

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


MIN(read)将返回“ read”的最小值。他可能正在寻找最新电子邮件的“已读”标志。
Andomar

2

我用这两种方法来处理比所示方法更复杂的查询,因为无论我使用什么索引,子查询方法都效率极低,并且因为我无法通过Hibernate获得外部自联接

最好(也是最简单)的方法是对某些东西进行分组,这些东西被构造为包含所需字段的串联,然后使用SELECT子句中的表达式将其拉出。如果需要执行MAX(),请确保要对其进行MAX()的字段始终位于串联实体的最高有效端。

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

如果您可以获取一个插入/更新事件(如触发器)来预先计算字段的串联,则可以对其进行索引,并且查询的速度将与group by刚好位于您实际要最大化的字段上一样)。您甚至可以使用它来获取多个字段的最大值。我用它来查询表示为嵌套集的多维树。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.