Doctrine 2 DQL - Select rows where a many-to-many field is empty?
Asked Answered
G

4

26

I have two classes in this example - DeliveryMethod and Country. They have a many-to-many relationship with each other.

What I want to do is select all DeliveryMethods that do not have any Countries mapped to them.

I can do the opposite, that is select all delivery methods that have at least one country -

SELECT m FROM DeliveryMethod m JOIN m.countries

But I can't figure out how to do select where the countries field is empty. In plain SQL I would do the following (deliverymethod_country is the linking table):

SELECT m.* FROM deliverymethods m
LEFT JOIN deliverymethod_country dc ON dc.deliverymethod_id = m.id
WHERE dc.deliverymethod_id IS NULL

However any DQL equivalent of this doesn't work, for example:

SELECT m FROM DeliveryMethod m LEFT JOIN m.countries WHERE m.countries IS NULL

Which gives me this error:

[Syntax Error] line 0, col 75: Error: Expected end of string, got 'm'
Glanville answered 9/5, 2012 at 10:11 Comment(0)
L
23

What about this? Assuming $qb is your query builder instance

$qb->select('m')
   ->from('DeliveryMethods','m')
   ->leftJoin('m.countries','c')
   ->having('COUNT(c.id) = 0')
   ->groupBy('m.id');

This would give you the DeliveryMethods which is associated with countries and count of the associated countries is 0

Lama answered 9/5, 2012 at 12:48 Comment(9)
Almost perfect, just needed to change ->join() to ->leftJoin() because otherwise it limits the query to methods that have at least one country and returns an empty result.Glanville
I am having a smiliar problem, maybe you can help? #14388297Nonattendance
@Lama what about one-to-one relations?Silvie
@parisssss I guess the same should work in One-To-One as well. On second thought, if the left entity is the owning side, you can just use $qb->expr()->isNull()Lama
@Lama thanks!!it's just worked with $qb->expr()->isNull() :)Silvie
@Lama brilliant been struggling with this for hours.Wedlock
This isn’t working on my end. The other solution submitted by @Kinescope works like a charmBombshell
using a having clause mean that the database have to retrieve the rows and then filter them this is counterproductive. You should use @Kinescope 's solution.Melamie
As stated by @Melamie above, both for readability and performance the 'm.countries is empty' should be used.Roquelaure
K
85

Use Doctrine's is empty

It's specifically designed to check for empty associations:

$qb->select('m')->from('DeliveryMethods', 'm')->where('m.countries is empty')

See: Doctrine 2 ORM Documentation: Doctrine Query Language (search for "is empty")

Kinescope answered 12/10, 2016 at 12:43 Comment(3)
Best answer. I am looking for a way to use this within an Expr context, see #41936397Nutritious
This should be the top answer.Hysteric
Just one remark - if you are building the query with join, you must use leftJoinConsumption
L
23

What about this? Assuming $qb is your query builder instance

$qb->select('m')
   ->from('DeliveryMethods','m')
   ->leftJoin('m.countries','c')
   ->having('COUNT(c.id) = 0')
   ->groupBy('m.id');

This would give you the DeliveryMethods which is associated with countries and count of the associated countries is 0

Lama answered 9/5, 2012 at 12:48 Comment(9)
Almost perfect, just needed to change ->join() to ->leftJoin() because otherwise it limits the query to methods that have at least one country and returns an empty result.Glanville
I am having a smiliar problem, maybe you can help? #14388297Nonattendance
@Lama what about one-to-one relations?Silvie
@parisssss I guess the same should work in One-To-One as well. On second thought, if the left entity is the owning side, you can just use $qb->expr()->isNull()Lama
@Lama thanks!!it's just worked with $qb->expr()->isNull() :)Silvie
@Lama brilliant been struggling with this for hours.Wedlock
This isn’t working on my end. The other solution submitted by @Kinescope works like a charmBombshell
using a having clause mean that the database have to retrieve the rows and then filter them this is counterproductive. You should use @Kinescope 's solution.Melamie
As stated by @Melamie above, both for readability and performance the 'm.countries is empty' should be used.Roquelaure
A
16

There is no need in joins and havings. Simply use SIZE function:

$qb->select('m')
   ->from('DeliveryMethods','m')
   ->where('SIZE(m.countries) = 0');

This will give you all methods without attached countries

Alva answered 8/10, 2014 at 14:54 Comment(1)
the better answer, leaves out exceptions where you might need another query because of the 'having'. doctrine will figure out the best strategy, it will for example do an subselect if the relation isnt fetch-joined.Habitation
P
0

Can't join NULL values, IIRC. Apologies for the typo on twitter, should have said "can't".

Packing answered 9/5, 2012 at 12:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.