简体   繁体   中英

How to query on related entities but return root entities?

This is a hypothetical question, because I'm trying to get my head around Doctrine ORM and having a hard time replicating what I'd do in plain SQL.

Suppose I have a simple ManytoMany relationship between tags and posts. They would map so that Post::tags is the owning side and Tag::posts is the reverse mapping.

I understand that with Doctrine's DQL I can select posts containing their tags, or tags referencing their posts with the following 2 queries

(1) SELECT p, t FROM MyBundle:Post p JOIN p.tags t WHERE p.id = :id

(2) SELECT t, p FROM MyBundle:Tag t JOIN t.posts p WHERE t.id = :id

But when I want to fetch posts by a number of tags, I have to choose between these:

(3) SELECT p, t FROM MyBundle:Post p JOIN p.tags t WHERE t.value IN ('foo','bar')

(4) SELECT t, p FROM MyBundle:Tag t JOIN t.posts p WHERE t.value IN ('foo','bar')

Both of these seem wrong.

With (3) I imagine the DB would scan the whole posts table before reducing the set to those tagged

With (4) I get a collection of tag objects back which is the inverse of what I'm after.

I tried the following, because logically it seemed to mirror what I'd do in SQL:

SELECT p, pt FROM MyBundle:Tag t JOIN t.posts p JOIN p.tags pt
                 WHERE t.value IN ('foo','bar') GROUP BY p.id

It doesn't work because Doctrine insists I select the root entity

What's the best way to select on tags but get back unique posts as full objects?

If I understood well the question, what you want is select all posts that have at least one tag whose value is in a defined set ("foo", "bar", ...) and ignore all other posts.

Let start from (3):

SELECT p, t FROM MyBundle:Post p JOIN p.tags t WHERE t.value IN ('foo','bar')

In MySQL this means approximately: "Select all posts having a tag whose value is either foo or bar".

However in DQL this means "Select all Posts and attach only tags whose values is either foo or or bar". Meaning that the query will return the set of all posts, but with tags filtered. This difference of behavior is due to the fact that doctrine has to create objects with the array returned by the Database Layer. When you make a MySQL query with JOINs, the results will be an array with duplicates. However, in the object world, duplicate posts have to be merged in a single Post object...

I believe the solution to your query is to filter when the join is made:

SELECT p,t FROM MyBundle:Post p JOIN p.tags t WITH t.value IN ('foo', 'bar')

you can understand the WITH DQL clause as if it added a condition in the ON MySQL clause. Ie the above query is similar to MySQL query below:

SELECT p.*, t.* FROM posts AS p JOIN posts_tags AS pt ON pt.post_id = p.id JOIN tags AS t ON pt.tag_id = t.id AND t.value IN ("foo", "bar")

Remark that there is no more WHERE clause. The condition moved in the JOIN condition clause...

Hope this helps ;-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM