关于MySql 对GroupBy进行排序的解决方案

本文详细解析了MySQL中GROUP BY语句的使用方法及其限制,尤其关注如何结合ORDER BY进行有效排序,以实现从不同主题帖子中选取最新回复的需求。
很简单。。

SELECT * FROM (SELECT * FROM posts ORDERBY dateline DESC) GROUP BY  tid ORDER BY datelineDESC LIMIT 10

以上例句,查询回复表,Group By tid,按照主帖的ID(tid)进行分类,根据dateline(回复时间)字段来进行排序。

由于Group By 必须写在 Order By 前面,所以对Group By的结果进行排序,所以必须在GroupBy之前进行排序,上面例句使用的是子查询的方式,在子查询中先进行排序。另外,更加推荐大家使用join关联的方式来进行此类分类排序操作,效率更高些。

http://blog.sina.com.cn/s/blog_68578afd0100nrka.html

--------------------------------------------------------------------------------------------------------------------------------------------

http://blog.youkuaiyun.com/microji/article/details/3880975

类如 有一个 帖子的回复表,posts( id , tid , subject , message ,  dateline ) ,

id为 自动增长字段, tid为该回复的主题帖子的id(外键关联),  subject 为回复标题, message 为回复内容, dateline 为回复时间,用UNIX 时间戳表示,

现在要求 选出 前十个来自不同主题的最新回复


SELECT * FROM posts GROUP BY  tid  LIMIT 10



这样一个sql语句选出来的并非你想要的 最新的回复,而是最早的回复,实际上是某篇主题的第一条回复记录!

也就是说 GROUP BY 语句没有排序,那么怎么才能让 GROUP 按照 dateline 倒序排列呢?加上 order by 子句?

看下面:

SELECT * FROM posts GROUP BY  tid  ORDER BY dateline DESC LIMIT 10


这条语句选出来的结果和上面的完全一样,不过把结果倒序排列了,而选择出来的每一条记录仍然是上面的记录,原因是 group by 会比 order by 先执行,这样也就没有办法将 group by 之前,也就是在分组之前进行排序了, 有网友会写出下面的sql 语句:


SELECT * FROM posts GROUP BY  tid DESC ORDER BY dateline DESC LIMIT 10



也就是说 在 GROUP BY 的字段 tid 后面加上递减顺序,这样不就可以取得分组时的最后回复了吗?这个语句执行结果会和上面的一模一样,这里加上 DESC 和ASC对执行结果没有任何影响!其实这是一个错误的语句,原因是GROUP BY 之前并没有排序功能,mysql 手册上面说,GROUP BY 时是按照某种顺序排序的,某种顺序到底是什么顺序?其实根本没有顺序,因为按照tid分组,其实也就是说,把tid相等的归纳到一个组,这样想的话,GROUP BY tid DESC 可以认为是在按照 tid 分组的时候,按照tid进行倒序排列,这不扯吗,既然是按照tid分组,当然是tid相等的归到一组,而这时候按照tid倒叙还是升序有个P用!


于是有网友发明下面的语句:


SELECT * FROM posts GROUP BY  tid , dateline DESC ORDER BY dateline DESC LIMIT 10



心想这样我就可以在分组前按照  dateline 倒序排列了,其实这个语句并没有起到按照tid分组的作用,原因还是上面的,在group by 字段后加 desc 还是 asc 是错误的写法,而这种写法 网友本意是想 按照 tid 分组,并且在分组的时候按照 dateline排倒序!而实际这句相当于下面的写法:(去掉 GROUP BY 字段后面的 DESC)


SELECT * FROM posts GROUP BY  tid , dateline ORDER BY dateline DESC LIMIT 10


也就是说,按照 tid 和 dateline 联合分组,只有在记录tid和dateline 同时相等的时候才归纳到一组,这显然不可能, 因为 dateline 时间线基本上是唯一的!


有人写出下面的语句:


SELECT *,max(dateline) as max_line FROM posts GROUP BY  tid ORDER BY dateline DESC LIMIT 10


这条语句的没错是选出了最大发布时间,但是你可以对比一下 dateline 和 max_dateline 并不相等!(可能有相当的情况,就是分组的目标记录只有一条的时候!)


为什么呢?原因很简单,这条语句相当于是 在group by 以后选出 本组的最大的 发布时间!对分组没有起到任何影响!因为SELECT子句是最后执行的!

后来更有网友发明了下面的写法!


SELECT *,max(dateline) as max_line FROM posts GROUP BY  tid HAVING dateline=max(dateline)


ORDER BY dateline DESC LIMIT 10



这条语句的预期结果和想象中的并不相同!因为你会发现,分组的结果中大量的记录没有了!为什么?因为 HAVING 是在分组的时候执行的,也就说:在分组的时候加上一个这样的条件:选择出来的 dateline 要和 本组最大的dateline 相等,执行的结果和下面的语句相同:


SELECT *,max(dateline) as max_line FROM posts GROUP BY  tid HAVING count(*)=1


ORDER BY dateline DESC LIMIT 10


看了这条sql语句是不是明白了呢?

dateline=max(dateline) 只有在分组中的记录只有一条的时候才成立,原因很明白吧!只有一条他才会和本组的最大发布时间相等阿,(默认dateline为不重复的值)


原因还是因为 group by 并没有排序功能,所有的这些排序功能只是错觉,所以你最终选出的 dateline 和max(dateline) 永远不可能相等,除非本组的记录只有一条!GROUP BY 在分组的时候,可能是一个一个来找的,发现有相等的tid,去掉,保留第一个发现的那一条记录,所以找出来的 记录永远只是按照默认索引顺序排列的!


那么说了这么多,到底有没有办法让 group by 执行前分组阿?有的 ,子查询阿!


最简单的 :


SELECT * FROM (SELECT * FROM posts ORDER BY dateline DESC) GROUP BY  tid ORDER BY dateline DESC LIMIT 10



也有网友利用自连接实现的 ,这样的效率应该比上面的子查询效率高,不过,为了简单明了,就只用这样一种了,GROUP BY没有排序功能,可能是mysql弱智的地方,也许是我还没有发现,

期待高人拍砖!

查看评论
3楼 lwxhami2011-08-02 17:39发表 [回复]
为什么一定要在条语句里实现呢,可以拆分一下:
1. 先取 pidlist
SELECT max(pid) FROM posts GROUP BY tid
2. 再取帖子内容
SELECT * FROM posts WHERE pid IN (pidlist) ORDER BY dateline
2楼 daiwei8292010-10-22 10:58发表 [回复]
[e02]
1楼 Leask2010-08-31 13:16发表 [回复]
SELECT * FROM (SELECT * FROM posts ORDER BY dateline DESC) GROUP BY tid ORDER BY dateline DESC LIMIT 10

子查询得到的表需要一个别名,如果没有,会出错的,你的语句查不到,应该是:

SELECT * FROM (SELECT * FROM posts ORDER BY dateline DESC) AS NEW GROUP BY tid ORDER BY dateline DESC LIMIT 10

<think>好的,我现在需要解决用户的问题:如何在MySQL中对已经排序的表结果再次进行二次排序。首先,我需要理解用户的具体需求。用户提到已经有一个排序后的结果,想在此基础上再次排序。这通常涉及到多层排序或者对子查询结果进行进一步排序的情况。 根据用户提供的引用内容,引用[1]提到MySQL中的排序策略,包括索引排序文件排序。这可能涉及到优化排序性能的问题,但用户的问题更关注于如何操作,而不是性能。引用[2]引用[3]展示了使用变量进行排名子查询的例子,特别是引用[3]中的例子,在子查询中进行了一次排序,然后在外部查询中再次排序。这可能是用户需要的二次排序方法。 接下来,我需要考虑MySQL中实现二次排序的几种方法。常见的方式包括使用子查询或派生表,在内部进行第一次排序,然后在外部进行第二次排序。此外,用户可能需要结合变量来实现更复杂的排序,比如分组内的排名。 首先,子查询的方式。例如,内部查询按字段A排序,外部查询按字段B排序。这种情况下,可能需要使用ORDER BY在子查询外部查询中各指定一次。但需要注意,MySQL在某些情况下可能会忽略子查询中的ORDER BY,除非使用LIMIT。因此,可能需要强制子查询保留排序结果,比如通过添加LIMIT子句,但这可能影响性能。 其次,使用派生表。将第一次排序结果作为派生表,然后在外部查询中再次排序。例如: SELECT * FROM (SELECT * FROM table ORDER BY col1) AS sub ORDER BY col2; 这种方法是否有效?需要验证。根据MySQL的文档,派生表内部的ORDER BY如果没有LIMIT可能会被优化掉,所以可能需要添加LIMIT来保证排序。但用户可能不希望限制结果数量,这需要权衡。 另外,引用[2]中的例子使用了变量在子查询中进行排序,然后在外部查询中处理。比如,先按nameclazz排序,然后在外部使用变量生成rank。这可能是一种二次排序的应用场景,即先按某些字段排序,再生成排名,然后可能再按其他字段排序。 用户的问题可能是在已经有一个排序结果集上,比如分组后的数据,需要再次进行排序。例如,先按部门分组并排序,再在部门内按薪水排序。这种情况下,可能需要使用多个ORDER BY条件,或者在子查询中处理。 可能的解决方案步骤: 1. 使用子查询或派生表,内部进行第一次排序,外部进行第二次排序。但需要注意MySQL对子查询ORDER BY的处理方式。 2. 使用多个排序字段,直接在单个ORDER BY子句中指定优先级。例如,ORDER BY col1, col2。这样可能不需要二次排序,而是组合排序。 但用户明确提到已经排序结果需要再次排序,可能是指分步骤处理。比如,先按A排序后的结果,再按B排序,而不是同按AB排序。 例如,先按总分排序,再按姓名排序。这种情况下,可能需要先按总分排序生成一个结果集,然后在这个结果集上再按姓名排序。 此,可能需要将第一次排序结果作为临表,然后在外部查询中再次排序。但需要注意,如果第一次排序结果没有被正确保留,可能需要使用表或者变量来强制保持顺序。 另外,考虑用户提供的引用[3]中的例子,他们在子查询中进行了排序,并在外部查询中使用变量生成序号。这说明子查询中的排序是有效的,可能因为子查询的结果被物化,从而保留了排序顺序。 因此,可能的正确方法是: 在子查询中进行第一次排序,然后在外部查询中进行第二次排序。例如: SELECT * FROM ( SELECT * FROM table ORDER BY col1 ) AS sorted_by_col1 ORDER BY col2; 但需要验证这种方法是否可行。根据MySQL的行为,如果子查询中没有LIMIT,优化器可能会忽略内部的ORDER BY,因此可能需要添加LIMIT子句,比如LIMIT 18446744073709551615(最大值),这样可以保留排序结果。 另外,用户可能需要处理更复杂的场景,比如对分组后的结果进行二次排序。例如,先按部门分组并按平均工资排序,然后在每个部门内按员工工资排序。这候可能需要使用窗口函数或者变量来处理。 总结可能的解决方案: 1. 使用子查询并添加LIMIT来强制保留第一次排序结果,然后在外部进行第二次排序。 2. 直接使用ORDER BY多个字段,如果二次排序的字段优先级不同,调整ORDER BY的顺序。 3. 使用变量在子查询中生成排名或其他计算,然后在外部查询中进行排序。 需要根据具体情况选择方法。同,需要注意性能问题,尤其是当处理大数据,多次排序可能导致性能下降,可能需要考虑索引优化。 例如,用户可能有一个已经按名称排序结果,现在想在此基础上按班级排序。这候,正确的做法可能是在同一个ORDER BY子句中指定多个字段,如ORDER BY name, clazz。但如果用户确实需要分两步进行,比如中间有处理步骤,则需要使用子查询。 最后,需要给出示例代码,并解答相关问题,如性能影响、优化方法等。同,根据引用内容,可能涉及到文件排序索引排序的选择,但用户的问题更关注操作而非性能优化,所以可能需要简要提及相关优化点。</think>在MySQL中对已排序结果进行二次排序,需要通过嵌套查询或派生表的方式实现。以下是具体方法示例: ### 一、基础实现方法 1. **子查询+ORDER BY组合** 将第一次排序结果作为子查询,在外部查询中再次排序: ```sql SELECT * FROM ( SELECT name, score FROM students ORDER BY score DESC -- 第一次排序(按分数降序) ) AS sorted_scores ORDER BY name ASC; -- 第二次排序(按名称升序) ``` 2. **强制保留子查询排序** 若子查询结果未保留排序,可添加`LIMIT`强制保留: ```sql SELECT * FROM ( SELECT * FROM employees ORDER BY department_id LIMIT 18446744073709551615 -- 最大值保证全量数据 ) AS sub ORDER BY salary DESC; ``` ### 二、分组排序后再排序 通过用户变量实现分组内排名后,再整体排序: ```sql SELECT name, department, rank FROM ( SELECT name, department, @rank := IF(@dept = department, @rank + 1, 1) AS rank, @dept := department FROM employees ORDER BY department, salary DESC -- 第一次排序(部门+薪资) ) AS ranked ORDER BY rank, name; -- 第二次排序(排名+名称) ``` ### 三、性能注意事项 1. **文件排序影响** 二次排序可能触发`Using filesort`[^1],可通过以下方式优化: - 为排序字段添加联合索引(如`(department, salary)`) - 减少`SELECT *`,仅查询必要字段 2. **临表限制** 嵌套查询会生成临表,数据量大可能影响性能。可通过调整`tmp_table_size`参数优化[^1]。 ### 四、典型应用场景 1. 排行榜二次筛选(总分排序后按年龄排序) 2. 分组报表生成(先按部门统计,再按统计值排序) 3. 动态排名计算(如先按销售额排序生成排名,再按区域排序) ```sql -- 示例:销售数据二次排序 SELECT region, total_sales, sales_rank FROM ( SELECT region, SUM(amount) AS total_sales, RANK() OVER(ORDER BY SUM(amount) DESC) AS sales_rank FROM orders GROUP BY region ORDER BY total_sales DESC -- 第一次排序 ) AS sub ORDER BY region; -- 第二次排序 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值