MySQL在进行group by操作之前先进行order by排序。
MySQL在进行group by操作之前先进行order by排序。
这里可以找到很多类似的问题,但我认为没有一个能充分回答这个问题。\n我会从当前最流行的问题继续,并使用他们的示例,如果可以的话。\n这个例子中的任务是从数据库中获取每个作者的最新帖子。\n示例查询产生的结果不可用,因为它不总是返回最新的帖子。\n
SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY wp_posts.post_author ORDER BY wp_posts.post_date DESC
\n当前被接受的答案是\n
SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY wp_posts.post_author HAVING wp_posts.post_date = MAX(wp_posts.post_date) <- 只有每个作者的最新帖子 ORDER BY wp_posts.post_date DESC
\n不幸的是,这个答案是错误的,并且在许多情况下产生的结果比原始查询更不稳定。\n我最好的解决方案是使用以下形式的子查询\n
SELECT wp_posts.* FROM ( SELECT * FROM wp_posts ORDER BY wp_posts.post_date DESC ) AS wp_posts WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY wp_posts.post_author
\n我的问题很简单:\n有没有办法在分组之前对行进行排序而不使用子查询?\n编辑:这个问题是从另一个问题延续过来的,我的情况稍微有些不同。你可以(也应该)假设还有一个wp_posts.id,它是该特定帖子的唯一标识符。
MySQL中为什么GROUP BY前可以使用ORDER BY,以及解决方法
在一般的SQL语句中,对于这个问题的答案是不可以,但是由于MySQL中的GROUP BY
模式相对宽松,所以答案是可以的。
假设你有一个BTREE索引(post_status, post_type, post_author, post_date)。在底层,这个索引是什么样子的呢?
(post_status='publish', post_type='post', post_author='user A', post_date='2012-12-01')
(post_status='publish', post_type='post', post_author='user A', post_date='2012-12-31')
(post_status='publish', post_type='post', post_author='user B', post_date='2012-10-01')
(post_status='publish', post_type='post', post_author='user B', post_date='2012-12-01')
数据按照这些字段的升序排列。
当你使用GROUP BY
时,默认情况下它会按照分组字段(post_author,在我们的例子中;post_status, post_type是由于WHERE
子句的要求)对数据进行排序,并且如果有匹配的索引,它会取每个分组中的第一条数据,按照升序排列。也就是说,查询会得到以下结果(每个用户的第一篇文章):
(post_status='publish', post_type='post', post_author='user A', post_date='2012-12-01')
(post_status='publish', post_type='post', post_author='user B', post_date='2012-10-01')
但是MySQL中的GROUP BY
允许你显式地指定排序方式。当你按照post_author的降序请求时,它将会按照相反的顺序遍历我们的索引,仍然取每个分组的第一条记录,但实际上是最后一条记录。
也就是说
... WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY wp_posts.post_author DESC
会得到
(post_status='publish', post_type='post', post_author='user B', post_date='2012-12-01')
(post_status='publish', post_type='post', post_author='user A', post_date='2012-12-31')
现在,当你对分组的结果按照post_date排序时,你将得到想要的数据。
SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY wp_posts.post_author DESC ORDER BY wp_posts.post_date DESC;
注意:
对于这个特定的查询,这不是我推荐的方法。在这种情况下,我会使用提供的稍微修改过的版本。但是这个技巧可能非常有用。请看我在这里的回答:检索每个分组中的最后一条记录
潜在的问题:这种方法的缺点是
- 查询的结果取决于索引,这与SQL的精神是相悖的(索引只应该加快查询);
- 索引对查询的影响一无所知(你或者将来的某个人可能会发现索引过于耗费资源,并进行一些更改,从而破坏查询结果,而不仅仅是性能)
- 如果你不理解查询的工作原理,很可能过一段时间你会忘记这个解释,然后这个查询会使你和你的同事感到困惑。
优点是在复杂情况下提高了性能。在这种情况下,查询的性能应该与的查询相同,因为涉及到大量的数据排序(所有数据都加载到临时表中,然后进行排序;顺便说一句,他的查询也需要(post_status, post_type, post_author, post_date)索引)。
我的建议:
正如我所说的,这些查询会使MySQL浪费时间在临时表中对可能巨大的数据进行排序。如果需要分页(涉及到LIMIT),大部分数据甚至会被丢弃。我会尽量减少排序数据的数量:也就是在子查询中对最小的数据进行排序和限制,然后与整个表进行连接。
SELECT * FROM wp_posts INNER JOIN ( SELECT max(post_date) post_date, post_author FROM wp_posts WHERE post_status='publish' AND post_type='post' GROUP BY post_author ORDER BY post_date DESC -- LIMIT放在这里 ) p2 USING (post_author, post_date) WHERE post_status='publish' AND post_type='post';
使用上述方法的同样查询:
SELECT * FROM ( SELECT post_id FROM wp_posts WHERE post_status='publish' AND post_type='post' GROUP BY post_author DESC ORDER BY post_date DESC -- LIMIT放在这里 ) as ids JOIN wp_posts USING (post_id);
所有这些查询及其执行计划可以在SQLFiddle上查看。
这是一个有趣的技巧。两件事:你说不要在家里尝试,潜在的陷阱是什么?其次,你提到了对bluefeet的答案稍作修改,那会是什么?
谢谢你的回答,很有趣地看到有人以不同的方式解决这个问题。由于我的数据集远远不及你的1800万+行,所以我认为性能不像可维护性那样重要,所以我认为后面的选项可能更合适。我喜欢子查询中的限制的想法。
MySQL中的order by在group by之前的问题产生的原因是MySQL在使用group by扩展的时候,允许对一些字段进行分组(在这种情况下只有post_author),并且选择非聚合的列(即没有在group by子句中列出的列,也没有在聚合函数中使用的列,例如MIN、MAX、COUNT等)。
这种扩展的正确用法是当非聚合列的所有值对于每一行都相等时,这样的情况下是有用的。如果需要提取在一个花园中生长的所有花朵,但是在一个花园中有多种花朵生长,可以使用子查询来实现。
如果需要提取那些是花园中唯一花朵的所有花朵,可以将HAVING条件改为HAVING COUNT(DISTINCT flower)=1,但是MySQL也允许使用以下方式:
SELECT GardensFlowers.* FROM GardensFlowers GROUP BY name HAVING COUNT(DISTINCT flower)=1;
虽然这不是标准SQL,但更简单。
然而,当选择非聚合列的值不相同时,MySQL选择的值是不确定的。因此,在进行GROUP BY之前对查询进行排序是无法解决这个问题的。
为了确保MySQL选择的是期望的第一个值,需要对查询进行排序,并使用子查询。以下是一个可能的解决方法:
SELECT wp_posts.* FROM wp_posts WHERE id IN ( SELECT max(id) FROM wp_posts WHERE (post_author, post_date) = ( SELECT post_author, max(post_date) FROM wp_posts WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY post_author ) AND wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY post_author )
在内部查询中,返回每个作者的最大发布日期。然后考虑到同一个作者理论上可能同时拥有两篇文章,所以只获取最大ID。然后返回具有这些最大ID的所有行。这个查询可以使用JOIN来提高性能。
总之,正确使用group by扩展和解决order by在group by之前的方法是通过使用子查询,对查询进行排序,并仔细阅读MySQL的文档以了解其行为。
MySQL中的"ORDER BY"在"GROUP BY"之前的问题是因为MySQL允许在子查询中使用"ORDER BY",而其他数据库产品如SQL Server则不允许。然而,使用"ORDER BY"在子查询中可能会导致错误的结果。为了解决这个问题,可以使用子查询返回每个作者的最大日期,并将其与表进行连接,连接条件是"post_author"和最大日期。以下是解决方案的示例代码:
SELECT p1.* FROM wp_posts p1 INNER JOIN ( SELECT max(post_date) MaxPostDate, post_author FROM wp_posts WHERE post_status='publish' AND post_type='post' GROUP BY post_author ) p2 ON p1.post_author = p2.post_author AND p1.post_date = p2.MaxPostDate WHERE p1.post_status='publish' AND p1.post_type='post' ORDER BY p1.post_date DESC
这个查询将返回每个作者的最新文章。当你在子查询中使用"ORDER BY"时,你可以确保每次执行查询时都能返回正确的结果。另外,这个查询在过滤数据时使用了两个条件,一个是"post_status",一个是"post_type",这样可以确保只返回已发布的文章。这个查询的效率可能会比仅使用子查询要稍微慢一些,但它能确保返回正确的结果。