Select count() in Doctrine DQL with left join manyToMany unidirectional relation where user does NOT have relation specific group
Asked Answered
S

2

8

Situaction: I am trying to select count() in DQL for users NOT in specific group.

Standard ManyToMany unidirectional relation between User and Group entities from FOSUserBundle (and SonataUserBundle). System: Symfony 2.5, Doctrine 2.4.

Entity User

P.S. this is not real code copied. It is not possible because there are several layers extending with different config files in different formats and places, so if you spot mistype, this is not the problem.

namespace RAZ\UserBundle\Entity;
/**
 * @ORM\Table(name="fos_user_user")
 */
class User
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var Collection
     *
     * @ORM\ManyToMany(targetEntity="Group")
     * @ORM\JoinTable(name="fos_user_user_group")
     */
    protected $groups;
}

Entity Group

namespace RAZ\UserBundle\Entity;
/**
 * @ORM\Table(name="fos_user_group")
 */
class Group
{
/**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;
}

Question: can this even be done in DQL?

Question very similar to: How to get entities in a many-to-many relationship that do NOT have a corresponding linked entity with DQL and Doctrine? The difference is I need to check for only one specific group.

Working SQL (returns 1423):

SELECT COUNT(*) cnt
FROM fos_user_user u
LEFT JOIN fos_user_user_group dug ON u.id = dug.user_id AND dug.group_id = 70
WHERE dug.user_id IS NULL

Incorrectly working DQL (returns 3208):

SELECT COUNT(u)
FROM RAZUserBundle:User u
LEFT JOIN u.groups dug WITH dug.id = 70
WHERE dug IS NULL

Problem is DQL generates different SQL:

SELECT COUNT(u.id)
FROM fos_user_user u
LEFT JOIN fos_user_user_group ug ON u.id = ug.user_id
LEFT JOIN fos_user_group g ON g.id = ug.group_id AND (g.id = 70)
WHERE g.id IS NULL

Any suggestions?

Spotted answered 17/9, 2014 at 9:25 Comment(6)
I think you may have to define your ManyToMany intermediate table as a separate entity (connected to User and Group with OneToMany and ManyToOne relationships, and potentially with extra code in the entities to handle that). You can then use @Matthew A Thomas's suggestion, I think it's not working currently because you just don't have a UserGroup entity.Sabulous
It's also interesting that the generated SQL gives the wrong answer, have you identified why that is? Is it that the LEFT JOIN onto fos_user_user_group is returning a lot of nulls for Users with no groups at all? Only thing occurring to me right now is to do two queries, and subtract the number who do match from the total.Sabulous
Yes, LEFT JOIN is returning null for each group.Spotted
So you are saying it is impossible select count in current implementation?Spotted
I don't know for certain, but I'm not aware of a way. There's always raw SQL, if you don't want to alter your entities?Sabulous
I used native SQL where it was possible, but there are some big DQL queries in project that would extremely complicated to redo it in SQL. I still think it's odd I cannot select this...Spotted
S
6

The only way I managed to do it in DQL is use subquery:

SELECT COUNT(u)
FROM RAZUserBundle:User u
WHERE u.id NOT IN (
    SELECT u2.id
    FROM RAZUserBundle:User u2
    JOIN u2.groups g WITH g.id = 70
)
Spotted answered 19/9, 2014 at 8:7 Comment(0)
D
9

I don't think your DQL is quite right. Can you post it? But in the meantime, this should work.

   $em = $this->getDoctrine()->getManager();
   $qb = $em->createQueryBuilder();

   $result = $qb->select('COUNT(u)')
                ->from('UserBundle:User' , 'u')
                ->leftJoin('u.UserGroup','g')
                ->where('g.GroupId = :id')
                ->andWhere('g.UserId = :null')
                ->setParameter('id', 70)
                ->setParameter('null', null)
                ->getQuery()
                ->getOneOrNullResult();

Also writing your DQL this way is easier to read ;)

Dieterich answered 17/9, 2014 at 9:41 Comment(5)
Updated question to include more specific details and changed bidirectional to unidirectional (sorry, noticed that only now).Spotted
Your example is not working for me. There is not joining entity UserGroup. User has groups but joining table is only in sql.Spotted
You have updated your question since my response, please post entitiesDieterich
Updated question with entities.Spotted
@Aure, you might try leftJoin('u.UserGroup','g', 'WITH', 'u.groupProperty= g.id'), replace groupProperty with appropriate relation parameter. See https://mcmap.net/q/1325054/-how-do-i-create-an-efficient-dql-statement-to-match-my-efficient-sql-when-doing-a-simple-left-joinLegionnaire
S
6

The only way I managed to do it in DQL is use subquery:

SELECT COUNT(u)
FROM RAZUserBundle:User u
WHERE u.id NOT IN (
    SELECT u2.id
    FROM RAZUserBundle:User u2
    JOIN u2.groups g WITH g.id = 70
)
Spotted answered 19/9, 2014 at 8:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.