Доктрина 2 DQL - как да изберете обратната страна на еднопосочна заявка много към много?

Имам два класа - Page и 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), но има ли някакъв начин без това да се направи или да се направи двупосочно?


person Gnuffo1    schedule 25.03.2011    source източник
comment
Здравей, Gnuffo1, би ли могъл да приемеш отговора на @Ocramius, мисля, че няма съмнение, че това реши проблема ти.   -  person Mick    schedule 07.01.2014


Отговори (5)


Има два начина за справяне с това в Doctrine 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-joined или обединени резултати.

person Ocramius    schedule 16.03.2013
comment
Това все още ли е най-добрата практика за този сценарий или има нещо за това сега в доктрина 2.5? - person Steffen Brem; 16.03.2016
comment
Да, това не се промени. - person Ocramius; 16.03.2016
comment
Също така бих искал да знам дали това оказва влияние върху производителността (частта 1=1)? В противен случай ще трябва да потърся друго решение за това, имам много DQL заявки, които трябва да се присъединят по този начин. - person Steffen Brem; 03.11.2016
comment
Не, приличните SQL програмисти премахват сравнението, преди да изпълнят заявката. - person Ocramius; 07.11.2016

Мисля, че трябва да изберете и SiteVersion във вашата заявка:

SELECT v, p FROM SiteVersion v JOIN v.pages p WHERE v.id = 5 AND p.slug='index'

Ще получите масив от обекти SiteVersion, през които можете да преминете, за да получите обектите на страницата.

person rojoca    schedule 25.03.2011
comment
Това ще извлече-присъедини страниците на сайта към версиите на сайта, връщайки списък от SiteVersion обекти, а не само страниците на сайта. Това всъщност е грешно предвид очаквания набор от резултати (според въпроса). - person Ocramius; 16.03.2013

Не можах да разбера как да накарам родните заявки да работят, така че разреших по малко хакерски начин:

$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'
person Gnuffo1    schedule 25.03.2011
comment
Всъщност нямате нужда от асоцииране, ако този вид преминаване е рядко. Да, това опростява нещата, но получавате някои недостатъци в производителността, когато хидратирате вашите обекти. - person Ocramius; 16.03.2013

Опитайте това (или нещо подобно):

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 Примери раздел на главата за DQL.

person Ian Phillips    schedule 11.07.2012

Намерих възможно решение за този проблем тук.

Според тази страница вашата заявка трябва да изглежда по следния начин:

SELECT p FROM SiteVersion v, Page p WHERE v.id = 5 AND p.slug='index' AND v.page = p;

Решава ли проблема ви?

person Imi Borbas    schedule 25.03.2011
comment
Не. Проблемът е, че е много към много, а не един към много. Това е v.pages, а не v.page, но дори и да го направя: SELECT p FROM SiteVersion v, Page p WHERE v.id = 5 AND p.slug='index' AND v.pages = p това също не работи ( Грешка: Невалиден PathExpression. Очаква се StateFieldPathExpression или SingleValuedAssociationField.) - person Gnuffo1; 25.03.2011
comment
Тогава защо не посочите другата страна на връзката в дефиницията на вашата схема? - person Imi Borbas; 25.03.2011
comment
Защото искам да мога просто да пусна този модул, без да засягам нищо, което вече е там. - person Gnuffo1; 25.03.2011
comment
Използването на собствен sql за избор на поне идентификаторите на страницата е жизнеспособна опция за вас? Разбира се, би било много по-чисто да се използва DQL... - person Imi Borbas; 25.03.2011