如何GROUP BY并按分组取N条结果?MySQL及Yii2 Active Query实现
如何在SQL中按分组获取每组前N条记录(附MySQL与Yii2实现)
嘿,这个需求我之前做项目的时候刚好碰到过,给你梳理一下具体的解决思路和实现方案,不管是原生SQL还是Yii2的写法都给你安排上:
一、先搞懂核心逻辑:GROUP BY为啥不能直接限制每组N条?
首先得澄清一个误区:普通的GROUP BY是用来做聚合统计的(比如统计每个分类的文章数量、总阅读量),它会把同组的记录合并成一行,没法直接返回每组的N条明细记录。要实现「按分组取每组前N条」的需求,核心思路是:
- 给每个分组内的记录,按照你要的排序规则(比如日期从新到旧)编号
- 最后筛选出编号≤N的记录,就得到每组的前N条
接下来针对你具体的MySQL场景和Yii2需求来拆解:
二、MySQL中获取每个分类最新N篇文章的SQL实现
假设你的articles表有id、category、date、title这些核心字段,需求是按date倒序,取每个category下最新的2篇文章(对应你例子里的N=2),分两种写法:
方法1:窗口函数(MySQL 8.0+首选,简洁高效)
MySQL 8.0及以上支持窗口函数,这是最推荐的写法,逻辑清晰性能也不错:
SELECT id, category, date, title FROM ( SELECT *, -- 按category分组,每组内按date倒序编号 ROW_NUMBER() OVER (PARTITION BY category ORDER BY date DESC) AS row_num FROM articles ) AS ranked_articles -- 只保留每组前2条 WHERE row_num <= 2;
如果有同日期的文章,且你希望同日期的文章也严格按唯一标识排序(比如id),可以把排序规则改成ORDER BY date DESC, id DESC,避免编号混乱。
方法2:关联子查询(兼容MySQL 5.x版本)
如果你的MySQL版本低于8.0,不支持窗口函数,就用关联子查询的方式:
SELECT a1.* FROM articles a1 WHERE ( -- 统计同分类下,日期比当前文章新(或相同)的文章数量 SELECT COUNT(*) FROM articles a2 WHERE a2.category = a1.category AND (a2.date > a1.date OR (a2.date = a1.date AND a2.id > a1.id)) ) <= 2 ORDER BY a1.category, a1.date DESC;
这里加了a2.id > a1.id的判断,是为了避免同日期的文章被重复统计,确保每个分类严格返回2条。如果不需要这么严格,去掉id的判断就行。
三、转换为Yii2 Active Query写法
Yii2的Active Query可以把上面的SQL转换成链式调用,同样分两种情况:
情况1:MySQL 8.0+用窗口函数的写法
use yii\db\Expression; use app\models\Article; // 替换成你的Article模型类 $limitPerCategory = 2; // 每组取2条 // 用子查询的方式更清晰 $subQuery = Article::find() ->select([ '*', // 定义窗口函数 new Expression('ROW_NUMBER() OVER (PARTITION BY category ORDER BY date DESC) AS row_num') ]); // 主查询筛选编号≤2的记录 $query = Article::find() ->from(['ranked_articles' => $subQuery]) ->where(['<=', 'row_num', $limitPerCategory]) ->orderBy(['category' => SORT_ASC, 'date' => SORT_DESC]); // 执行查询,得到结果 $articles = $query->all(); // 返回ActiveRecord对象数组 // 或者用asArray()返回关联数组:$articles = $query->asArray()->all();
情况2:兼容MySQL 5.x的关联查询写法
use yii\db\Expression; use app\models\Article; $limitPerCategory = 2; $query = Article::find() ->alias('a1') ->where(['<=', // 子查询统计同分类下更晚的文章数量 new Expression('(SELECT COUNT(*) FROM articles a2 WHERE a2.category = a1.category AND (a2.date > a1.date OR (a2.date = a1.date AND a2.id > a1.id)))'), $limitPerCategory ]) ->orderBy(['a1.category' => SORT_ASC, 'a1.date' => SORT_DESC]); $articles = $query->all();
内容的提问来源于stack exchange,提问作者sirjay
相关产品推荐
相关产品推荐

