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

Laravel查询构建器按p_id分组取每组前2条数据报错求助

解决Laravel分组取每组前N条数据的SQL错误问题

首先得说你原来的写法有两个核心问题:一是触发了MySQL严格模式下的1055报错,二是逻辑上根本达不到「每组取前2条」的目的。

先解释报错原因:MySQL现在默认开启严格模式,GROUP BY子句要求SELECT里的字段要么是分组字段(这里就是p_id),要么是用聚合函数处理过的字段(比如MAX(time))。你原来的SELECT直接放了u_id和time,既不是分组字段也没加聚合函数,数据库自然会抛出语法违规的错误。

而且就算关掉严格模式,你的逻辑也走不通——ORDER BY在GROUP BY之前执行的话,分组操作只会把每组的所有行合并成一行,根本没法保留组内的前2条数据,完全不符合你的需求。

下面给你两种可行的解决方案,根据你的MySQL版本选择就行:

方案一:用窗口函数(推荐,MySQL 8.0+支持)

窗口函数是处理这类「分组取前N条」需求的最优解,用ROW_NUMBER()就能轻松实现:

// 先给每组数据按time升序编号,再筛选出编号<=2的行
$result = DB::table('a16s')
    ->select(
        'p_id',
        'u_id',
        'time',
        DB::raw('ROW_NUMBER() OVER (PARTITION BY p_id ORDER BY time ASC) as row_num')
    )
    ->having('row_num', '<=', 2)
    ->orderBy('p_id', 'asc')
    ->orderBy('time', 'asc')
    ->get();

// 如果不需要返回row_num字段,再做个简单处理
$finalResult = $result->map(function ($item) {
    unset($item->row_num);
    return $item;
});

echo '<pre>'; print_r($finalResult);

这里的PARTITION BY p_id相当于按p_id分组,ORDER BY time ASC是每组内按时间从小到大排序,ROW_NUMBER()会给每组里的行从1开始编号,最后筛选出编号≤2的行,正好就是你要的每组前2条数据。

方案二:子查询方式(兼容MySQL 5.7及更早版本)

如果你的MySQL版本还不支持窗口函数,可以用子查询统计每行在组内的排名:

$result = DB::table('a16s as main')
    ->select('main.p_id', 'main.u_id', 'main.time')
    ->whereRaw('(SELECT COUNT(*) FROM a16s as sub WHERE sub.p_id = main.p_id AND sub.time <= main.time) <= 2')
    ->orderBy('main.p_id', 'asc')
    ->orderBy('main.time', 'asc')
    ->get();

echo '<pre>'; print_r($result);

这个思路是:对于每一行,统计同一p_id下time小于等于当前行time的行数,如果这个行数≤2,说明当前行是组内time排前2的(因为time越小,统计出来的行数越少)。

两种方案都能得到你期望的结果:

p_id u_id time
1 2 0
1 1 1
2 1 0
2 3 1
3 4 8
3 2 11

内容的提问来源于stack exchange,提问作者robspin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:33:13