如何在Doctrine 2中将子查询结果追加到主查询结果末尾?
在Doctrine 2中将子查询结果追加到主查询结果末尾的实现方法
要实现主查询结果在前、子查询结果在后的合并效果,Doctrine Query Builder本身不直接支持UNION操作,你可以通过以下两种方式解决:
方法一:使用原生SQL结合UNION ALL(推荐,性能更优)
通过给主查询和子查询添加优先级字段,确保主查询结果排在前面,再用UNION ALL合并,同时避免重复数据:
public function findPosts(): array { $ids = [777, 775, 767, 756, 752, 751, 732, 727, 725, 721, 717, 710, 702, 693, 678, 671, 662, 658, 639, 638, 617, 598, 579]; $presentDate = date('Y-m-d H:i:s'); // 构建主查询:排除子查询中的ID避免重复,添加优先级字段1 $qbMain = $this->createQueryBuilder('article') ->select('article.id', 'article.title', 'article.slug', 'article.slots_type', 'article.skill', 'article.type', 'article.views', 'article.vk_shares', 'article.fb_shares', 'image.filename AS img', '1 AS priority') ->leftJoin('article.img', 'image') ->where('article.visible = 1') ->andWhere('article.published_at <= :present_date') ->andWhere($qbMain->expr()->notIn('article.id', ':ids')) ->setParameter('present_date', $presentDate) ->setParameter('ids', $ids) ->orderBy('article.id', 'DESC') ->setMaxResults(30); // 构建子查询:添加优先级字段2 $qbSub = $this->createQueryBuilder('article_sub') ->select('article_sub.id', 'article_sub.title', 'article_sub.slug', 'article_sub.slots_type', 'article_sub.skill', 'article_sub.type', 'article_sub.views', 'article_sub.vk_shares', 'article_sub.fb_shares', 'image_sub.filename AS img', '2 AS priority') ->leftJoin('article_sub.img', 'image_sub') ->where('article_sub.id IN (:ids)') ->andWhere('article_sub.visible = 1') ->setParameter('ids', $ids) ->orderBy('article_sub.id', 'DESC'); // 拼接UNION ALL SQL并执行 $mainSql = $qbMain->getQuery()->getSQL(); $subSql = $qbSub->getQuery()->getSQL(); $unionSql = "$mainSql UNION ALL $subSql ORDER BY priority ASC, id DESC"; $conn = $this->getEntityManager()->getConnection(); $stmt = $conn->prepare($unionSql); $stmt->bindValue('present_date', $presentDate); $stmt->bindValue('ids', $ids, \Doctrine\DBAL\Types\Type::INTEGER); $stmt->execute(); return $stmt->fetchAllAssociative(); }
关键说明:
- 给两个查询分别添加
priority字段,主查询设为1,子查询设为2,最终通过ORDER BY priority ASC确保主结果在前。 - 主查询中用
notIn排除子查询的ID,避免同一篇文章重复出现。 - 将
ids改为数组并使用参数绑定,避免SQL注入风险,比直接拼接字符串更安全。
方法二:内存中合并两个查询结果(适合数据量较小的场景)
如果数据量不大,可以分别执行两个查询,再在内存中合并并去重:
public function findPosts(): array { $ids = [777, 775, 767, 756, 752, 751, 732, 727, 725, 721, 717, 710, 702, 693, 678, 671, 662, 658, 639, 638, 617, 598, 579]; $presentDate = date('Y-m-d H:i:s'); // 执行主查询 $mainResults = $this->createQueryBuilder('article') ->select('article.id', 'article.title', 'article.slug', 'article.slots_type', 'article.skill', 'article.type', 'article.views', 'article.vk_shares', 'article.fb_shares') ->leftJoin('article.img', 'image') ->addSelect('image.filename AS img') ->where('article.visible = 1') ->andWhere('article.published_at <= :present_date') ->setParameter('present_date', $presentDate) ->orderBy('article.id', 'DESC') ->setMaxResults(30) ->getQuery() ->execute(); // 执行子查询 $subResults = $this->createQueryBuilder('article') ->select('article.id', 'article.title', 'article.slug', 'article.slots_type', 'article.skill', 'article.type', 'article.views', 'article.vk_shares', 'article.fb_shares') ->leftJoin('article.img', 'image') ->addSelect('image.filename AS img') ->where('article.id IN (:ids)') ->andWhere('article.visible = 1') ->setParameter('ids', $ids) ->orderBy('article.id', 'DESC') ->getQuery() ->execute(); // 合并结果并去重:保留主查询的项,再追加子查询中未出现的项 $mainIds = array_column($mainResults, 'id'); $finalResults = $mainResults; foreach ($subResults as $item) { if (!in_array($item['id'], $mainIds)) { $finalResults[] = $item; } } return $finalResults; }
关键说明:
- 先获取主查询结果,再遍历子查询结果,只追加主查询中没有的项,确保顺序和去重。
- 实现简单,不需要接触原生SQL,但数据量大时会增加内存开销和数据库查询次数。
关于FIELD函数无效的原因
FIELD函数仅能指定单个字段的自定义排序顺序,无法实现“将一组结果整体追加到另一组结果末尾”的分组排序需求。要实现这种分组顺序,必须通过添加优先级字段来区分两组数据,再基于该字段排序。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

