【问题标题】:trying to select where left join is null using doctrine query builder尝试使用学说查询生成器选择左连接为空的位置
【发布时间】:2019-10-21 12:30:52
【问题描述】:

我正在尝试选择所有在给定预订日期未被占用的房间。
我已经创建了这样的代码:

$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 个预订,其中房间 ID 等于预订 ID:

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

【问题讨论】:

    标签: php doctrine-orm doctrine symfony4 query-builder


    【解决方案1】:

    您可能希望排除预订与查询日期范围重叠的房间。

    那是

    $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/

    您的查询匹配所有具有所有预订的房间(不仅仅是该房间的预订),并选择那些“没有”预订的房间。含义:如果在日期范围内有预订,则返回 NO 房间,如果在日期范围内没有预订,则返回所有房间。

    有两种方法可以让查询构建器做正确的事情,这取决于您的映射定义,但我会假设 Room 实体有一个字段 reservations 包含该房间的所有预订。

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

    不同之处在于,“桌子”描述符暗示预订属于各自的房间,并且只与那些房间相连。

    或者是明确的:

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

    这应该最终解决你的问题。

    【讨论】:

    • 非常感谢,乍一看似乎您是对的。我回家后会尝试一下,并将您的答案标记为解决方案,如果仅此而已。其次是更复杂的方法,我担心我只是误解了表达式的工作原理(别名也搞砸了,因为我把实体结构弄乱了很多^^')
    • tbh,反正我不是特别喜欢expr() 的东西。我相信,在 99% 的用例中,与 DQL 相比没有什么重要的收获。
    • 就我个人而言,我发现 queryBuilder 比纯 DQL 更具可读性,尤其是当您基于许多不同的条件动态构建查询时。
    • 哦,我不写纯 DQL,我只是避免使用 expr() 的东西。我看不出andWhere($qb-&gt;expr()-&gt;isNull('reservation')) 在任何方面都比andWhere('reservation IS NULL') 更好。我真的很喜欢这样一个事实,即使用 querybuilder 可以忽略添加内容的顺序,因此查询生成在代码中非常清晰(显然不一定,但可以这样做)
    • 哦,您在某些范围内是对的。 “与”说,我想要标准连接条件加上以下内容。然而,仅仅说“我想加入房间和预订”本身并没有说,room.reservations 默认情况下是加入条件。我的意思是,你可以说不同的话。事实上,标准的连接条件只是......“真”,它将一切与一切连接起来。此外,doctrine-project.org/projects/doctrine-orm/en/latest/reference/… 上的 所有 示例展示了如何使用 u.Group 而不仅仅是 Group 进行连接。所以......假设不多。
    【解决方案2】:

    上面的答案使我走上了正确的道路,以了解我的失败。
    我的最终代码如下所示:

    $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。关联在实体类中得到了很好的定义,如果我指向对象的字段而不是一般类,Doctrine 将添加适当的约束。
    Jakumi 给出的示例适用于 ManyToOne 关联,在我的情况下 (ManyToMany),我必须手动加入 Reservation 和 Room 之间的连接表。

    【讨论】:

    • 不,实际上,这也适用于多对多。 ;o) (除非您的多对多表现为具有两个多对一/一对多关系的自己的实体)
    • 我的意思是我不能只做 r.room = rm,因为 Reservation 实体中没有“房间”字段。 Room 实体中有“房间”字段和“预订”字段。也许有办法以这种方式加入,但我不明白如何^^'
    • 我同意,目前还不清楚r.rooms = rm 会/可能意味着什么,rm IN r.rooms 可能会起作用。但是,是的,多对多可能需要更多的努力。
    猜你喜欢
    • 2016-04-02
    • 1970-01-01
    • 2011-11-03
    • 2013-09-29
    • 2022-01-20
    • 1970-01-01
    • 2014-05-13
    • 2017-01-12
    • 2012-12-07
    相关资源
    最近更新 更多