Symfony/Doctrine实体中的关系计数

11

正如标题所述,我想运行一个查询来从一个表中获取它们各自关系的计数结果。

假设我有一个名为Person的实体,它与一个Friend实体具有OneToMany关系。

Person实体可能看起来像以下内容:

class Person
{
    /**
     * @ORM\OneToMany(...)
     */
    protected $friends;

    public function __construct()
    {
        $this->friends = new ArrayCollection();
    }

    ...
}

我想要实现的一个经典 SQL 解决方案可能如下所示:

SELECT p.*, COUNT(f.id) as friendsCount
FROM Persons p
LEFT JOIN Friends f
ON f.person_id = p.id
GROUP BY p.id

现在我在想是否可以在DQL中完成这个任务,并将“count”值存储到“Person”实体中。假设我像下面这样扩展“Person”实体:(请记住,这只是一个想法)
class Person
{
    /**
     * @ORM\OneToMany(...)
     */
    protected $friends;

    protected $friendsCount;

    public method __construct()
    {
        $this->friends = new ArrayCollection();
    }

    ...

    public function getFriendsCount()
    {
        return $this->friendsCount;
    }
}

现在我遇到了困难,无法找到如何从DQL中填充实体中的计数值:

SELECT p, /* What comes here ? */
FROM AppBundle\Entity\Person p
LEFT JOIN p.friends f
GROUP BY p.id

PS: 我知道我可以直接这样做:

$person->getFriends()->count();

甚至可以将其标记为extra lazy以获取计数结果。

我认为这个“计数关系”的示例很好地展示了我想做的事情。

(即从dql填充实体的非@ORM\Column属性)

Doctrine可以做到吗?

这会违反一些稳固原则吗?(SRP?)

谢谢您的留言;)


如果您使用Select p, SIZE(p.friends) FROM Person p LEFT JOIN p.friends f ....,无法正常工作! - Francisco
1个回答

12
您可能只需要按照上面所述使用$person->getFriends()->count();选择所需的计数。但是,您也可以同时选择对象和计数(请参见这些Doctrine查询示例),其中有一个与您正在进行的非常相似:
SELECT p, COUNT(p.friends)
FROM AppBundle\Entity\Person p
LEFT JOIN p.friends f
GROUP BY p.id

应该返回一个包含对象和计数的数组的数组,如下所示:
[
    [Person, int],
    [Person, int],
    ...,
]

您最好的选择是在您的PersonRepository上进行存储库调用,类似于findPersonsWithFriendCount(),如下所示:
public function findPersonsWithFriendCount()
{
    $persons = array();

    $query = $this->_em->createQuery('
        SELECT p, COUNT(p.friends)
        FROM AppBundle\Entity\Person p
        LEFT JOIN p.friends f
        GROUP BY p.id
    ');

    $results = $query->getResult();

    foreach ($results as $result) {
        $person = $result[0];
        $person->setFriendsCount($result[1]);

        $persons[] = $person;
    }

    return $persons;
}

请记住,您需要在您的Person对象上添加一个setFriendsCount()函数。您还可以编写一个本地SQL查询,并使用结果集映射自动将原始结果的列映射到实体字段。


非常感谢这个例子!真的很有帮助。 - RVandersteen
1
请记住,这并不是必需的。如果您只是像这样显式地加入存储库调用,您可以删除 COUNT() 并像往常一样返回实体,然后在 getFriendsCount() 方法中放置 return $this->friends->count()。如果您显式加入它,它不会添加另一个数据库调用...如果您尚未加载它们,则会这样做。 - Jason Roman
确实是个好点子,我甚至可能不需要显式地使用getFriendsCount(),而是在连接后需要时使用getFriends()->count()。但是这种技术对于某些特殊查询可能会很有用。对于非常大的关系数量和性能问题,您有什么想法?连接(因此水化)大量实体只是为了获取计数可能会减慢速度。只是随便想想(写出来):) - RVandersteen
我的工作方式通常是首先使用ORM和最简单的方法来完成...然后如果出现性能问题,开始查看类似原始查询、移除hydration、缓存等方面的内容。我尽量不会过度预优化,除非像使用联接来防止页面运行50个额外查询之类的情况。 - Jason Roman
这确实是一种有效的做事方式。谢谢你分享你的想法。 - RVandersteen
1
我不得不更新原始查询为 SELECT p,COUNT(f)... 以使其起作用。 - Dimitry K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接