Symfony 3.4中OneToMany关联查询筛选关联数据失效问题
问题原因分析
你遇到的问题核心在于Doctrine的关联加载逻辑:
- 你在QueryBuilder里写的
LEFT JOIN ... WITH条件,只是用来筛选出哪些Offer会被返回(比如即使某个Offer没有符合条件的Price,也会被保留在结果里)。 - 但Doctrine默认会对关联的
prices集合使用懒加载:当你访问某个Offer的prices属性时,它会单独执行一条SQL查询该Offer下的所有Price记录,完全忽略你之前在JOIN里加的过滤条件。这就是为什么你看到结果里返回了所有Price的原因。
而且从你给出的SQL也能看出来,这条查询只查了offer表的字段,根本没涉及price的详细数据——后面加载Price是Doctrine自动触发的额外查询。
解决方案
针对这个需求,有几种实用的解决办法,你可以根据场景选择:
方法1:使用Fetch Join(推荐)
在QueryBuilder里明确同时选择Offer和符合条件的Price,让Doctrine一次性把两者的数据都查出来,而不是后续懒加载所有Price。修改后的代码如下:
$qb = $this->createQueryBuilder('o') ->select('o', 'p') // 关键:明确选中o和符合条件的p ->leftJoin('o.prices', 'p', 'WITH', 'p.dateEnd >= :now OR p.dateEnd IS NULL') ->setParameter('now', $nowDate); return $qb->getQuery()->getResult();
这样生成的SQL会同时包含offer和price的字段,Doctrine会直接把查询到的符合条件的Price填充到对应的Offer的prices集合里,不会再去加载所有Price了。
方法2:全局过滤关联(适合固定需求)
如果你在大部分场景下都只需要加载符合dateEnd >= 当前时间 或 为NULL的Price,可以直接在Offer实体的关联映射里添加过滤条件:
// Offer.php /** * @ORM\OneToMany(targetEntity="Price", mappedBy="offer") * @ORM\Where("dateEnd IS NULL OR dateEnd >= CURRENT_DATE()") */ private $prices;
这样每次加载Offer的prices集合时,都会自动应用这个过滤条件。不过要注意,这是全局生效的,如果有其他场景需要加载所有Price,这种方法就不适用了。
方法3:子查询筛选(适合复杂场景)
如果你的查询逻辑更复杂(比如需要结合分页),可以用子查询先筛选出符合条件的Price对应的Offer ID,再关联查询:
// 先获取所有符合条件的Price对应的Offer ID $subQb = $this->getEntityManager()->createQueryBuilder() ->select('p.offerId') ->from('AppBundle:Price', 'p') ->where('p.dateEnd >= :now OR p.dateEnd IS NULL'); // 再查询Offer并关联符合条件的Price $qb = $this->createQueryBuilder('o') ->select('o', 'p') ->leftJoin('o.prices', 'p', 'WITH', 'p.dateEnd >= :now OR p.dateEnd IS NULL') ->where('o.id IN (:validOfferIds)') ->setParameter('now', $nowDate) ->setParameter('validOfferIds', $subQb->getQuery()->getScalarResult()); return $qb->getQuery()->getResult();
内容的提问来源于stack exchange,提问作者Clément Andraud
相关产品推荐
相关产品推荐

