Doctrine 2 DQL - как выбрать обратную сторону однонаправленного запроса "многие ко многим"?
У меня есть два класса - Страница и SiteVersion, у которых есть много разных отношений. Только SiteVersion знает об отношениях (потому что сайт является модульным, и я хочу убрать и удалить модуль, к которому принадлежит SiteVersion).
Как бы я выбрал страницы на основе критериев SiteVersion?
Например, это не работает:
SELECT p FROM SiteVersion v JOIN v.pages p WHERE v.id = 5 AND p.slug='index'
Я получаю сообщение об ошибке:
[Doctrine\ORM\Query\QueryException]
[Semantical Error] line 0, col -1 near 'SELECT p FROM': Error: Cannot select entity through identification variables without choosing at least one root entity alias.
Даже если я могу выбрать "v" с этим запросом.
Я думаю, что я мог бы решить это, введя класс для отношения (класс PageToVersion), но есть ли какой-либо способ без этого или сделать его двунаправленным?
Ответы
Ответ 1
Есть два способа обработки этого в ORM доктрины. Наиболее типичным является использование условия IN
с подзапросом:
SELECT
p
FROM
SitePage p
WHERE
p.id IN(
SELECT
p2.id
FROM
SiteVersion v
JOIN
v.pages p2
WHERE
v.id = :versionId
AND
p.slug = :slug
)
Другой способ заключается в дополнительном соединении с функцией произвольного соединения, представленной в версии 2.3 ORM:
SELECT
p
FROM
SitePage p
JOIN
SiteVersion v
WITH
1 = 1
JOIN
v.pages p2
WHERE
p.id = p2.id
AND
v.id = :versionId
AND
p2.slug = :slug
1 = 1
происходит только из-за текущего ограничения синтаксического анализатора.
Обратите внимание, что ограничение, вызывающее семантическую ошибку, связано с тем, что процесс гидратации начинается с корня выбранных объектов. Без корня на месте гидратор не имеет никаких указаний о том, как свернуть fetch-join или объединить результаты.
Ответ 2
Думаю, вам нужно также выбрать SiteVersion в вашем запросе:
SELECT v, p FROM SiteVersion v JOIN v.pages p WHERE v.id = 5 AND p.slug='index'
Вы получите массив объектов SiteVersion, которые вы можете пропустить, чтобы получить объекты страницы.
Ответ 3
Я не мог понять, как работать с родными запросами, поэтому решил немного взломать:
$id = $em->getConnection()->fetchColumn("SELECT
pages.id
FROM
pages
INNER JOIN siteversion_page ON siteversion_page.page_id = pages.id
INNER JOIN siteversions ON siteversion_page.siteversion_id = siteversions.id
WHERE siteversions.id = 1
AND pages.slug = 'index'");
$page = $em->find('Page', $id);
Мне это не нравится, потому что это приводит к большему количеству запросов к базе данных (особенно если мне нужно получить массив страниц вместо одного), но он работает.
Изменить. Я решил просто пойти с классом для ассоциации. Теперь я могу выполнить этот запрос:
SELECT p FROM Page p, SiteVersionPageLink l
WHERE l.page = p AND l.siteVersion = 5 AND p.slug = 'index'
Ответ 4
Попробуйте (или что-то в этом роде):
SELECT p FROM Page p WHERE EXISTS (SELECT v FROM SiteVersion v WHERE p MEMBER OF v.pages AND v.id = 5 AND p.slug = 'index')
Я не тестировал это точно, но я получил что-то похожее на работу. Использование EXISTS
и MEMBER OF
содержится в разделе DQL Select Examples главы DQL.
Ответ 5
Я нашел возможное решение этой проблемы здесь.
В соответствии с этой страницей ваш запрос должен выглядеть примерно так:
SELECT p FROM SiteVersion v, Page p WHERE v.id = 5 AND p.slug='index' AND v.page = p;
Решает ли ваша проблема?