Как запрашивать связанные объекты, но возвращать корневые объекты?

Это гипотетический вопрос, потому что я пытаюсь разобраться с Doctrine ORM и с трудом воспроизвожу то, что я сделал бы в простом SQL.

Предположим, у меня есть простая связь ManytoMany между тегами и сообщениями. Они будут отображать так, что Post::tags будет стороной-владельцем, а Tag::posts — обратным отображением.

Я понимаю, что с помощью DQL Doctrine я могу выбирать сообщения, содержащие их теги, или теги, ссылающиеся на их сообщения, с помощью следующих 2 запросов.

(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

Но когда я хочу получать посты по нескольким тегам, мне приходится выбирать между ними:

(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')

Оба они кажутся неправильными.

С (3) я предполагаю, что БД будет сканировать всю таблицу сообщений, прежде чем сокращать набор до тех, которые помечены

С помощью (4) я получаю набор объектов тегов, который является обратным тому, что мне нужно.

Я попробовал следующее, потому что логически это отражало то, что я делал бы в 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

Это не работает, потому что Doctrine настаивает на том, чтобы я выбрал корневую сущность

Каков наилучший способ выбрать теги, но получить уникальные сообщения в виде полноценных объектов?


person Tim    schedule 28.11.2012    source источник
comment
числа (3) и (4) имели смысл до того, как мой пост был отредактирован - надеюсь, понятно, что они относятся ко второму набору примеров операторов.   -  person Tim    schedule 28.11.2012
comment
Отредактировал редактирование для вас - теперь это снова имеет смысл ;)   -  person Thilo    schedule 28.11.2012
comment
Можете ли вы сделать что-то вроде SELECT p FROM MyBundle:Post p JOIN p.tags t WHERE t.value IN ('foo', 'bar') GROUP BY p.id? Какая информация от объекта вам нужна в конечном результате?   -  person Squazic    schedule 29.11.2012
comment
В идеале я бы хотел полностью гидратированные посты с их коллекциями тегов, но это не главное беспокойство по моему вопросу. Вы написали мой вариант 3 с группировкой, которую я забыл добавить. - как это позволяет избежать сканирования всей таблицы сообщений?   -  person Tim    schedule 29.11.2012
comment
Лучше всего использовать команду объяснения sql и посмотреть, что на самом деле делает сгенерированный sql. Затем сравните его с написанным от руки sql.   -  person Cerad    schedule 29.11.2012
comment
Ой, не заметил. Теперь я лучше понимаю ваш вопрос, и ответ Серада выглядит как лучший способ сделать это.   -  person Squazic    schedule 29.11.2012
comment
справедливое замечание по поводу команды объяснения, хотя я вполне уверен, что выбор сообщений приведет к сканированию всей таблицы. Я проведу несколько тестов, но на самом деле мне интересно узнать о лучших методах доктрины для такого рода задач.   -  person Tim    schedule 29.11.2012
comment
Конечно, построитель запросов просто позволяет вам создавать строку DQL программно. Какие дополнительные функции он предлагает, чтобы решить эту проблему?   -  person Tim    schedule 29.11.2012
comment
Вы также можете попробовать выполнить подзапрос для тегов. Что-то вроде: выберите * из сообщения, где находится post.id (выберите post_id из тега, где значение находится в ('foo', 'bar')). Похоже, что любой приличный оптимизатор запросов сможет избежать полного сканирования.   -  person Cerad    schedule 29.11.2012
comment
Хороший вызов подзапроса. Я попробую это в Doctrine и отчитаюсь   -  person Tim    schedule 29.11.2012


Ответы (1)


Если я хорошо понял вопрос, вы хотите выбрать все сообщения, у которых есть хотя бы один тег, значение которого находится в определенном наборе ("foo", "bar",...), и игнорировать все остальные сообщения.

Начнем с (3):

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

В MySQL это примерно означает: «Выбрать все сообщения, имеющие тег, значение которого равно foo или bar».

Однако в DQL это означает «Выбрать все сообщения и прикрепить только те теги, значения которых равны foo или bar». Это означает, что запрос вернет набор всех сообщений, но с отфильтрованными тегами. Эта разница в поведении связана с тем, что доктрина должна создавать объекты с массивом, возвращаемым уровнем базы данных. Когда вы делаете запрос MySQL с JOIN, результатом будет массив с дубликатами. Однако в мире объектов дубликаты сообщений должны быть объединены в один объект Post...

Я считаю, что решение вашего запроса заключается в фильтрации при выполнении соединения:

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

вы можете понимать предложение WITH DQL, как если бы оно добавляло условие в предложение ON MySQL. т.е. приведенный выше запрос аналогичен запросу MySQL ниже:

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")

Обратите внимание, что предложения WHERE больше нет. Условие перемещено в предложение условия JOIN...

Надеюсь это поможет ;-)

person Vincent Pazeller    schedule 01.12.2012
comment
Спасибо за это. Я получаю часть WITH, я не подумал об этом. Тем не менее, мы по-прежнему сканируем ВСЕ сообщения, не так ли? Разве это не очень неэффективно? - person Tim; 01.12.2012
comment
Если вы создадите индекс для столбца tags.value (и, конечно, других столбцов внешнего ключа), система управления базами данных (СУБД) не будет сканировать всю таблицу сообщений, и ваш запрос должен быть достаточно эффективным. - person Vincent Pazeller; 03.12.2012
comment
О, я, возможно, не очень хорошо понял ваш последний комментарий. Я думаю, что мы не сканируем все сообщения, потому что СОЕДИНЕНИЕ является ВНУТРЕННИМ СОЕДИНЕНИЕМ (а не ЛЕВЫМ СОЕДИНЕНИЕМ). Следовательно, следует выбирать только те сообщения, которые имеют хотя бы один объединенный объект тега. Вот почему было важно переместить условие в предложение ON... - person Vincent Pazeller; 13.12.2012
comment
Выполнив несколько запросов EXPLAIN, я вижу, что вы абсолютно правы. Условие соединения приводит к точно таким же поискам, как выбор тега для начала. - person Tim; 02.05.2013