Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DQL many to many and count

Tags:

php

symfony

dql

I'm using Symfony 2 with Doctrine, and I've got two entities joined in a many to many association. Let's say I have two entities: User and Group, and the related tables on db are users, groups and users_groups.

I'd like to get the top 10 most populated groups in DQL, but I don't know the syntax to perform queries on the join table (users_groups). I already looked on the Doctrine manual but I didn't found the solution, I guess I still have a lot to learn about DQL.

In plain sql that would be:

select distinct group_id, count(*) as cnt from users_groups group by group_id order by cnt desc limit 10

Can you please help me to translate this to DQL?

Update (classes):

/**
 * Entity\E_User
 *
 * @ORM\Table(name="users")
 * @ORM\Entity
 */
class E_User
{
    /**
     * @ORM\ManyToMany(targetEntity="E_Group", cascade={"persist"})
     * @ORM\JoinTable(name="users_groups",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="cascade")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id", onDelete="cascade")}
     * )
     */

    protected $groups;

    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $name
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /* ... other attributes & getters and setters ...*/
}


/**
 * Entity\E_Group
 *
 * @ORM\Table(name="groups")
 * @ORM\Entity
 */
class E_Group
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $name
     *
     * @ORM\Column(name="text", type="string", length=255)
     */
    private $name;

    /* ... other attributes & getters and setters ...*/
}
like image 213
Lorenzo Marcon Avatar asked Apr 16 '12 14:04

Lorenzo Marcon


2 Answers

It's not easy without seeing the actual classes, but by guessing you have a many-to-many bidirectional relationship:

$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\Group g " .
    "JOIN g.users u GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
$query = $em->createQuery($dql);
$popularGroups = $query->getArrayResult();

UPDATE:

You don't have to use a bidirectional relationship, you can query the other way around:

$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\User u " .
    "JOIN u.groups g GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
like image 107
Tom Imrei Avatar answered Nov 08 '22 07:11

Tom Imrei


For those who want to build the query with Doctrine's QueryBuilder instead of using DQL directly take this solution.

Please note that my problem wasn't to get the top user groups, but technically the problem is pretty similar to mine. I work with posts (like articles/blog posts) and tags that are added to posts. I needed to determine a list of related posts (identified by same tags). That list had to be sorted by relevance (the more same tags another post has the more relevant it is).

This is the method of my PostRepository class:

/**
 * Finds all related posts sorted by relavance
 * (from most important to least important) using
 * the tags of the given post entity.
 *
 * @param Post $post
 *
 * @return POST[]
 */
public function findRelatedPosts(Post $post) {
    //  build query
    $q = $this->createQueryBuilder('p')
        ->addSelect('count(t.id) as relevance')
        ->innerJoin('p.tags', 't')
        ->where('t.id IN (:tags)')
        ->setParameter('tags', $post->getTags())
        ->andWhere('p.id != :post')
        ->setParameter('post', $post->getId())
        ->addGroupBy('p.id')
        ->addOrderBy('relevance', 'DESC')
        ->getQuery();

    //  execute query and retrieve database result
    $r = $q->execute();

    //  result contains arrays, each array contains
    //  the actual post and the relevance value
    //  --> let's extract the post entities and
    //  forget about the relevance, because the
    //  posts are already sorted by relevance
    $r = array_map(function ($entry) {
        //  first index is the post, second one
        //  is the relevance, just return the post
        return reset($entry);
    }, $r);

    //  array of posts
    return $r;
}

Thank you @Tom Imrei for you solution. Also the answer #26549597 was very helpful.

like image 41
Arvid Avatar answered Nov 08 '22 06:11

Arvid