пытаясь выбрать, где левое соединение является нулевым, с помощью построителя запроса доктрины - PullRequest
0 голосов
/ 21 октября 2019

Я пытаюсь выбрать все комнаты, которые не заняты в указанные даты бронирования. Я создал такой код:

$queryBuilder->select('rm')
            ->from('App:Room', 'rm')
            ->leftJoin('App:Reservation', 'r', 'WITH', $queryBuilder->expr()->andX(
                $queryBuilder->expr()->lt('r.start', '?1'),
                $queryBuilder->expr()->gt('r.stop', '?2'),
                $queryBuilder->expr()->neq('r.status', '?3')
                )
            )->where(
                $queryBuilder->expr()->andX(
                    $queryBuilder->expr()->isNull('r')
                )
            )->setParameters(
                array(
                    1 => $stop,
                    2 => $start,
                    3 => Reservation::STATUS_EXPIRED
                )
            ); 

У меня 2 бронирования на 2 комнаты, где идентификатор комнаты равен идентификатору бронирования:

  1. 2000-01-01 - 2000-01-02
  2. 2000-01-01 - 2000-01-03

Также $start = '2000-01-02' и $stop = '2000-01-05' и статус обоих бронирований Reservation::STATUS_NEWЯ ожидаю, что с учетом этих дат этот запрос вернет мне комнату 1., так как комната 2 имеет резервирование, сталкивающееся с ними, но результат будет пустым. Удаление предложения where приводит к возвращению обеих комнат. В этом случае $queryBuilder->getQuery()->getArrayResult() возвращает номера и номер бронирования 2. как и ожидалось, но когда я восстанавливаю предложение where, массив просто становится пустым. Мой другой подход - через подзапрос, но он ведет себя точно так же, поэтому, пожалуйста, посоветуйте, что мне не хватает?

$queryBuilder = $this->getEntityManager()->createQueryBuilder();
        $all = $this->getEntityManager()->createQueryBuilder();

        $all->select('rma.id')
            ->from('App:Room', 'rma')
            ->innerJoin('App:Reservation', 'r')
            ->where(
                $all->expr()->andX(
                    $all->expr()->lt('r.start', '?1'),
                    $all->expr()->gt('r.stop', '?2'),
                    $all->expr()->neq('r.status', '?3')
                )
            );

        $queryBuilder->select('rm')
            ->from('App:Room', 'rm')
            ->where(
                $queryBuilder->expr()->neq(
                    'rm.id',
                    $queryBuilder->expr()->all(
                        $all->getDQL()
                    )
                )
            )->setParameters(
                array(
                    1 => $stop,
                    2 => $start,
                    3 => Reservation::STATUS_EXPIRED
                )
            );

Ответы [ 2 ]

1 голос
/ 21 октября 2019

вы хотели бы исключить комнаты, где резервирования имеют перекрытие с диапазоном дат запроса.

, то есть

$queryBuilder->expr()->lt('r.start', '?1'), // 1 being stop
$queryBuilder->expr()->gt('r.stop', '?2'),  // 2 being start

, который вы уже правильно указали. однако ваша нулевая проверка выключена, она должна быть:

$queryBuilder->expr()->isNull('r') // <-- you had rm here

, поэтому, r, очевидно, является резервированием, и это должно быть нулевым («отсутствующим»).

(Я всегда нахожу, что использование коротких псевдонимов очень усложняет обнаружение таких ошибок, и при этом экономится очень мало фактического времени ..., я также предпочитаю именованные параметры над пронумерованными параметрами)

обновление

Хорошо, я пропустил главное, что здесь не так; o /

Ваш запрос соответствует всем комнатам с всеми бронированиями (не только бронированием этой комнаты) и выбирает те, которые «не имеют» брони. Значение: если есть резервирование для диапазона дат, вы возвращаете НЕТ номеров, а если нет резервирования для диапазона дат, вы возвращаете ВСЕ номера.

Есть два способа заставить построителя запросов выполнитьправильно, что зависит от вашего определения отображения, но я буду предполагать, что у сущности Room есть поле reservations, которое содержит все резервирования для этой комнаты.

$qb->from('Room', 'rm')
   ->leftJoin('rm.reservations', 'r', 'WITH', ...)

Разница в том, чтодескриптор "table" подразумевает , что резервирование относится к соответствующей комнате и объединено только с этим.

Или является явным:

$qb->from('Room', 'rm')
   ->leftJoin('Reservation', 'r', 'WITH', $qb->expr()->andX('r.room=rm', ...))

Это должно окончательно решить вашу проблему.

0 голосов
/ 24 октября 2019

Ответ выше поставил меня на правильный путь, чтобы понять мою неудачу. Мой окончательный код выглядит следующим образом:

$queryBuilder->select('rm')
            ->from('App:Room', 'rm')
            ->leftJoin('rm.reservations', 'r', 'WITH', $queryBuilder->expr()->andX(
                $queryBuilder->expr()->lt('r.start', '?1'),
                $queryBuilder->expr()->gt('r.stop', '?2'),
                $queryBuilder->expr()->neq('r.status', '?3')
                )
            )->where(
                $queryBuilder->expr()->andX(
                    $queryBuilder->expr()->isNull('r')
                )
            )->setParameters(
                array(
                    1 => $stop,
                    2 => $start,
                    3 => Reservation::STATUS_EXPIRED
                )
            );

Все было связано с присоединением rm.reservations, а не с общим App:Reservation, как я. Ассоциации хорошо определены внутри классов Entity, и Doctrine добавит надлежащие ограничения, если я укажу на поле объекта вместо общего класса. Пример, приведенный Jakumi, работал бы хорошо для ассоциации ManyToOne, в моем случае (ManyToMany) мне пришлось бы вручную соединить соединительную таблицу между Reservation и Room.

...