我正在寻找一种优雅的方式来执行以下操作:
假设我有一个具有OneToMany关系的实体,例如
class Parent
{
/**
* @ORM\OneToMany(targetEntity="Child")
*/
private $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
}
class Child
{
/**
* @ORM\ManyToOne(targetEntity="Parent")
*/
private $parent;
}
现在,在我的逻辑中,我必须过滤那些仅通过查询无法完成的父项。所以我最终得到了父母的ArrayCollection,例如:
$parents = new ArrayCollection([
$parent1,
$parent2,
$parent3
]);
现在,从所有这些父母那里,我想通过一个问题加入孩子们。 我怎么能用学说做到这一点。
我知道我可以循环收集并在每个父母上调用->getChildren()
。
但我可能有数百个父母(这意味着数百个查询)。我想避免的。
在有人说'当你得到父母的时候对父母进行左手加入'之前,我不能仅仅通过查询来过滤我需要的父母。所以这就是为什么我不能只是LEFT JOIN。
cookies for thoughs!
答案 0 :(得分:1)
为了避免N + 1 selects problem,我建议以下解决方案不需要JOIN并使用两个单独的查询。这是最有效的解决方案。
首先,检索所有父母:
$parents = $em->createQueryBuilder()
->select("p")
->from("YourFoobarBundle:Parent", "p")
->where(/*...*/)
->setParameter(/*...*/)
->indexBy("p.id")
->getQuery()->getResult();
现在加载这些父母的所有孩子:
$children = $em->createQueryBuilder()
->select("c")
->from("YourFoobarBundle:Child", "c")
->where("IDENTITY(child.parent) IN (?1)")
->setParameter(1, array_keys($parents))
->getQuery()->getResult();
关于Doctrine的好处是,现在所有需要的实体都存储在内存中。因此,当您执行$parent->getChildren()
时,不会触发新的数据库查询(除非子项本身具有其他关系)。
注意:如果始终需要所选父母的所有孩子,则应标记$children
以便加载:
/**
* @ORM\OneToMany(targetEntity="Child", fetch="EAGER")
*/
private $children;
在这种情况下,Doctrine将始终(!)自动获取所需的孩子。
答案 1 :(得分:1)
您可以创建子查询并在其他查询的子句(表达式中的where)中使用它。它非常像@lxg描述,但您可以在一个查询中完成所有这些以提高性能(您不必单独执行查询)。
$qb = $entityManager->createQueryBuilder();
$sub = $qb->select('p')
->from('Application\Entity\Parent', 'p')
->where(/*...*/)
->setParameter(/*...*/)
$children = $qb->select('c')
->from('Application\Entity\Child', 'c')
->where($qb->expr()->in('c.parent', $sub->getDQL()))
->getQuery()
->getResult();