如何用Doctrine Query Builder实现按分类/用户ID各取2条记录
嘿,这两个需求都是业务里常见的分组取TopN场景,我刚好处理过类似的情况,给你分享下具体实现方式:
需求1:用Doctrine Query Builder从每个分类取2条记录
首先得确认你的Doctrine ORM版本在2.6及以上——这个版本开始支持SQL窗口函数,这是实现这类需求最简洁高效的方案。假设你的实体是Category(分类)和Record(分类下的记录),关联关系是一个分类对应多条记录,代码可以这么写:
use Doctrine\ORM\EntityManagerInterface; public function fetchTwoRecordsPerCategory(EntityManagerInterface $em) { $queryBuilder = $em->createQueryBuilder(); // 核心是用ROW_NUMBER()窗口函数给每个分类下的记录编号 $queryBuilder ->select('record', 'category', 'ROW_NUMBER() OVER (PARTITION BY category.id ORDER BY record.createdAt DESC) as rowNum') ->from('App\Entity\Record', 'record') // 替换成你的记录实体类 ->join('record.category', 'category') // 替换成实体中关联分类的属性名 ->having('rowNum <= 2') // 只保留每个分类的前2条 ->orderBy('category.id', 'ASC') ->addOrderBy('record.createdAt', 'DESC'); $rawResults = $queryBuilder->getQuery()->getResult(); // 可选:把结果整理成按分类分组的结构,方便后续使用 $groupedResults = []; foreach ($rawResults as $item) { $categoryId = $item['category']->getId(); if (!isset($groupedResults[$categoryId])) { $groupedResults[$categoryId] = []; } $groupedResults[$categoryId][] = $item['record']; } return $groupedResults; }
关键逻辑说明:
PARTITION BY category.id:按分类ID对记录进行分组ORDER BY record.createdAt DESC:每个分类下按创建时间倒序,取最新的2条(如果要取最早的,改成ASC即可)having('rowNum <= 2'):筛选出每个分组内排名前2的记录
如果你的Doctrine版本较低不支持窗口函数,也可以用子查询关联的方式实现,但效率会差一些,优先推荐窗口函数方案。
需求2:按user_id从评论表选取2行数据
针对这个用户评论表,同样用窗口函数就能轻松实现,下面分原生SQL和Doctrine两种写法:
原生SQL示例(适配MySQL 8.0+/PostgreSQL等支持窗口函数的数据库)
假设你的表名叫user_comments,如果有自增的comment_id或者创建时间字段,用它来排序保证结果确定性:
SELECT user_id, comment FROM ( SELECT user_id, comment, -- 按user_id分组,给每条评论编号,按comment_id倒序取最新的2条 ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY comment_id DESC) as rowNum FROM user_comments ) as ranked_comments WHERE rowNum <= 2 ORDER BY user_id, rowNum;
如果没有自增ID或时间戳,也可以用comment字段排序,但更建议添加时间类字段来保证排序逻辑的稳定性。
Doctrine Query Builder实现
假设你的评论实体是UserComment,对应的代码如下:
use Doctrine\ORM\EntityManagerInterface; public function fetchTwoCommentsPerUser(EntityManagerInterface $em) { // 先写子查询,给每个用户的评论加上行号 $subQueryBuilder = $em->createQueryBuilder(); $subQueryBuilder ->select('uc', 'ROW_NUMBER() OVER (PARTITION BY uc.userId ORDER BY uc.id DESC) as rowNum') ->from('App\Entity\UserComment', 'uc'); // 替换成你的评论实体类 // 主查询筛选出行号<=2的记录 $mainQueryBuilder = $em->createQueryBuilder(); $mainQueryBuilder ->select('sub.uc') ->from('(' . $subQueryBuilder->getDQL() . ')', 'sub') ->where('sub.rowNum <= 2') ->orderBy('sub.uc.userId', 'ASC') ->addOrderBy('sub.uc.id', 'DESC'); return $mainQueryBuilder->getQuery()->getResult(); }
执行后就能得到每个user_id下的2条评论,和你预期的结果一致。
内容的提问来源于stack exchange,提问作者M Maavia
相关产品推荐
相关产品推荐

