You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:51:07