MySQL按前面的顺序排列
在这里可以找到很多类似的问题,但是我不认为这个问题有足够的答案。
我会继续从当前最流行的问题,并使用他们的例子,如果没关系。
在这个例子中,任务是获取数据库中每个作者的最新帖子。
示例查询产生不可用的结果,因为它并不总是返回的最新帖子。
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
目前接受的答案是
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) <- ONLY THE LAST POST FOR EACH AUTHOR ORDER BY wp_posts.post_date DESC
不幸的是,这个答案是简单明了的错误,在许多情况下,产生的结果比原始查询的结果要少。
我最好的解决方案是使用表单的子查询
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
那么我的问题是一个简单的问题: 无论如何,无需诉诸子查询就可以在分组之前对行进行排序?
编辑 :这个问题是从另一个问题的延续,我的情况细节略有不同。 你可以(也应该)假定还有一个wp_posts.id是该特定帖子的唯一标识符。
在子查询中使用ORDER BY
不是解决此问题的最佳方法。
获得作者max(post_date)
的最佳解决方案是使用子查询来返回最大日期,然后将它加入post_author
和max日期的表中。
解决方案应该是:
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
如果您有以下示例数据:
CREATE TABLE wp_posts (`id` int, `title` varchar(6), `post_date` datetime, `post_author` varchar(3)) ; INSERT INTO wp_posts (`id`, `title`, `post_date`, `post_author`) VALUES (1, 'Title1', '2013-01-01 00:00:00', 'Jim'), (2, 'Title2', '2013-02-01 00:00:00', 'Jim') ;
子查询将返回最大日期和作者:
MaxPostDate | Author 2/1/2013 | Jim
那么既然你加入了这个表格,那么你将返回这个帖子的全部细节。
看演示与SQL小提琴 。
为了扩大我对使用子查询准确地返回这些数据的意见。
MySQL并不强迫你将GROUP BY
包含在SELECT
列表中。 因此,如果只有GROUP BY
一列,但总共返回10列,则不能保证属于返回的post_author
的其他列值。 如果该列不在GROUP BY
MySQL选择应返回的值。
使用具有聚合函数的子查询将保证每次都返回正确的作者和帖子。
值得一提的是,虽然MySQL允许你在子查询中使用ORDER BY
,并允许你将GROUP BY
到SELECT
列表中的每一列,但是在包括SQL Server在内的其他数据库中不允许这种行为。
您的解决方案使用了GROUP BY子句的扩展 ,允许按某些字段进行分组(在本例中,只是post_author
):
GROUP BY wp_posts.post_author
并选择nonaggregated列:
SELECT wp_posts.*
没有在group by子句中列出,或者在聚合函数(MIN,MAX,COUNT等)中没有使用。
正确使用扩展到GROUP BY子句
当非聚合列的所有值对于每一行都相等时,这非常有用。
例如,假设你有一张桌子GardensFlowers
(花园的name
,在花园里生长的flower
):
INSERT INTO GardensFlowers VALUES ('Central Park', 'Magnolia'), ('Hyde Park', 'Tulip'), ('Gardens By The Bay', 'Peony'), ('Gardens By The Bay', 'Cherry Blossom');
你想要提取花园里生长的所有花朵,那里有多朵花朵。 然后你必须使用一个子查询,例如你可以使用这个:
SELECT GardensFlowers.* FROM GardensFlowers WHERE name IN (SELECT name FROM GardensFlowers GROUP BY name HAVING COUNT(DISTINCT flower)>1);
如果你需要提取花朵中所有花朵,那么你可以将HAVING条件改为HAVING COUNT(DISTINCT flower)=1
,但是MySql也允许你使用这个:
SELECT GardensFlowers.* FROM GardensFlowers GROUP BY name HAVING COUNT(DISTINCT flower)=1;
没有子查询,没有标准的SQL,但更简单。
对GROUP BY子句使用扩展名不正确
但是如果你选择非聚合的列对于每一行都是不相等的呢? MySql为该列选择的值是什么?
它看起来像MySql总是选择它遇到的FIRST值。
为了确保它遇到的第一个值恰好是您想要的值,您需要将GROUP BY
应用于有序查询,因此需要使用子查询。 否则你不能这样做。
假设MySql总是选择遇到的第一行,那么您正在对GROUP BY之前的行进行正确的排序。 但不幸的是,如果你仔细阅读文档,你会注意到这个假设是不正确的。
当选择不总是相同的非聚合列时, MySql可以自由选择任何值,因此实际显示的结果值是不确定的 。
我发现这个获得非聚合列的第一个值的技巧使用了很多,而且通常/几乎总是有效的,我有时也会使用它(在我自己的风险之下)。 但由于没有记录,你不能依赖这种行为。
这个链接(感谢ypercube!) GROUP BY技巧已经被优化了,显示了相同的查询在MySql和MariaDB之间返回不同结果的情况,可能是因为有不同的优化引擎。
所以,如果这个技巧有效,那只是一个运气问题。
另一个问题上接受的答案对我来说是错误的:
HAVING wp_posts.post_date = MAX(wp_posts.post_date)
wp_posts.post_date
是一个非聚合列,它的值将被正式确定,但它可能是遇到的第一个post_date
。 但是,由于GROUP BY技巧应用于无序表,所以不能确定哪个是遇到的第一个post_date
。
它可能会返回帖子,这是一个作者的唯一职位,但即使这并不总是确定的。
可能的解决方案
我认为这可能是一个可能的解决方案:
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的行。 使用连接而不是IN子句可以使速度更快。
(如果你确定ID
只是增加了,如果ID1 > ID2
也意味着post_date1 > post_date2
,那么查询可以变得更简单,但我不确定是否是这种情况)。
你要读的是相当黑,所以不要在家里试试这个!
在SQL中,一般来说,你的问题的答案是NO ,但是由于GROUP BY
的松散模式(由@bluefeet提到),MySQL中的答案是YES 。
假设你有一个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_user
时,它将以相反的顺序遍历我们的索引,仍然为每个实际上最后的组创建第一条记录。
那是
... 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;
注意 :
这不是我会推荐这个特定的查询。 在这种情况下,我会使用@bluefeet建议的稍微修改过的版本。 但是这个技术可能非常有用。 看看我的答案在这里: 检索每个组中的最后一个记录
陷阱 :这种方法的缺点是
- 查询的结果取决于索引,这违背了SQL的精神(索引只能加快查询速度);
- 索引不知道它对查询的影响(你或将来的其他人可能会发现索引太耗费资源,并以某种方式改变它,打破查询结果,而不仅仅是它的性能)
- 如果你不明白查询是如何工作的,那么很可能你会在一个月内忘记解释,查询会让你和你的同事感到困惑。
好处是在困难的情况下表现。 在这种情况下,查询的性能应该与@ bluefeet的查询相同,因为在排序中涉及的数据量(所有数据都被加载到一个临时表中,然后排序; btw,他的查询需要(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 GOES HERE ) 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 GOES HERE ) as ids JOIN wp_posts USING (post_id);
所有那些在SQLFiddle上执行计划的查询。
试试这个。 只要从每位作者那里得到最新发布日期的列表 。 而已
SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' AND wp_posts.post_date IN(SELECT MAX(wp_posts.post_date) FROM wp_posts GROUP BY wp_posts.post_author)
不。在分组之前排序记录是没有意义的,因为分组将会改变结果集。 子查询的方式是首选的方式。 如果这太慢了,你将不得不改变你的表格设计,例如把每个作者的最后一篇文章的id存储在一个单独的表格中,或者引入一个布尔列来指示每个作者的哪篇文章是最后的一。
简单来说,标准解决方案使用不相关的子查询,如下所示:
SELECT x.* FROM my_table x JOIN (SELECT grouping_criteria,MAX(ranking_criterion) max_n FROM my_table GROUP BY grouping_criteria) y ON y.grouping_criteria = x.grouping_criteria AND y.max_n = x.ranking_criterion;
如果您使用的是MySQL的古老版本或相当小的数据集,那么您可以使用以下方法:
SELECT x.* FROM my_table x LEFT JOIN my_table y ON y.joining_criteria = x.joining_criteria AND y.ranking_criteria < x.ranking_criteria WHERE y.some_non_null_column IS NULL;
只需使用最大功能和组功能
select max(taskhistory.id) as id from taskhistory group by taskhistory.taskid order by taskhistory.datum desc
**与大型数据集一起使用时,子查询可能会对性能造成不良影响**
原始查询
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;
修改后的查询
SELECT p.post_status, p.post_type, Max(p.post_date), p.post_author FROM wp_posts P WHERE p.post_status = "publish" AND p.post_type = "post" GROUP BY p.post_author ORDER BY p.post_date;
因为我在select clause
使用了max
==> max(p.post_date)
所以可以避免子选择查询和group by之后的max列排序。
首先,不要在选择中使用*,影响他们的表现,妨碍组织的使用。 试试这个查询:
SELECT wp_posts.post_author, wp_posts.post_date as pdate FROM wp_posts WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post' GROUP BY wp_posts.post_author ORDER BY pdate DESC
当你没有在ORDER BY中指定表时,只需要别名,他们就会命令select的结果。