排除学说QueryBuilder中的重叠期

Posted

技术标签:

【中文标题】排除学说QueryBuilder中的重叠期【英文标题】:Excluding overlapped period in doctrine QueryBuilder 【发布时间】:2021-10-25 15:57:36 【问题描述】:

我有ProductBooking 实体,我正在尝试选择在给定时间段内尚未预订的产品。换句话说,在给定时间段内选择可用的产品。

为了便于理解,请参阅下面的架构。我只想选择具有 #1 或 #5 等预订的产品,因为它可用。如果产品有 #2、#3、#4、#6 之类的预订,请不要选择它,因为它不可用。

每个|--| 代表一个句点,左侧为startAt 字段,右侧为endAt 字段。

Past                                              Futur 
|----------------------------------------------------->
                     Given period
                   |--------------|
     1             .              .
|---------|        .              .
                 2 .              .
             |-----+-|            .
                   .      3       .
                   .   |-----|    .
                   .              . 4
                   .            |-+-----|
                   .              .             5
                   .              .        |---------|
                   .       6      .
             |-----+--------------+-----|
                   .              .

我的猜测是创建这样的查询:

class ProductRepository extends EntityRepository

    public function getAvailableProducts(\DateTimeInterface $startAt, \DateTimeInterface $endAt): array
    
        return $this->createQueryBuilder('p')
            ->addSelect('b')
            ->join('p.bookings', 'b')
            ->andWhere('b.startAt > :endAt OR b.endAt < :startAt')
            ->setParameter('startAt', $startAt->format('Y-m-d H:i:s'))
            ->setParameter('endAt', $endAt->format('Y-m-d H:i:s'))
            ->getQuery()
            ->getResult()
        ;
    

但是当andWhere()条件在schema中遇到#1或#5时,即使#2、#3、#4或#6存在,数据库(mysql 5.6)也会选择这个产品。

我对这个查询有点卡住了,我觉得我走错了路,所以任何帮助都将不胜感激!

【问题讨论】:

您的情况看起来不错,see demo。你能分享一个 SQL 小提琴来重现这个问题吗? @nice_dev 你可以看看我的小提琴here @nice_dev 小提琴中的预期输出应该是没有结果,因为有一些预订与给定时间段重叠。但是通过这个查询,它向我展示了产品,因为 #1 和 #5 满足条件。 如果我理解正确,结果是正确的。 ['2022-01-05','2022-01-15'] 不与 ['2021-11-05','2021-11-10']['2022-02-01','2022-02-15'] 重叠。因此,它们都被检索了。 @nice_dev 这个查询的重点是确定产品是否可供预订。如果在给定期间['2022-01-05', '2022-01-15'] 存在预订,则该产品不可用。所以是的,这个查询的结果是正确的,但它不是正确的查询。我正在寻找一个仅输出可用产品的查询,但我在执行此操作时遇到了困难。 【参考方案1】:

产品在给定时间段内可能/可能没有预订。要检测重叠或碰撞,您最多需要检查 3 个条件:

当前行开始日期在所选日期期间之间 当前行结束日期在所选日期期间之间 所选期间的开始日期介于当前行的开始日期和结束日期之间。

原始 SQL 查询:

SELECT count(*) as 'booked_count'
FROM product p 
INNER JOIN booking b ON p.id = b.product_id 
WHERE (
        (b.startAt between '2022-01-05 00:00:00' and '2022-01-15 00:00:00') OR 
        (b.endAt between '2022-01-05 00:00:00' and '2022-01-15 00:00:00') OR 
        ('2022-01-05 00:00:00' between b.startAt and b.endAt)
    ) 
AND p.id IN (1,2);

Fiddle Demo

Doctrine QueryBuilder:

<?php

class ProductRepository extends EntityRepository

    public function getAvailableProducts(\DateTimeInterface $startAt, \DateTimeInterface $endAt): array
    
        return $this->createQueryBuilder('p')
            ->addSelect('count(b.*) as cnt')
            ->join('p.bookings', 'b')
            ->where('b.startAt between :startAt and :endAt')
            ->orWhere('b.endAt between :startAt and :endAt')
            ->orWhere(':startAt between b.startAt and b.endAt')
            ->setParameter('startAt', $startAt->format('Y-m-d H:i:s'))
            ->setParameter('endAt', $endAt->format('Y-m-d H:i:s'))
            ->getQuery()
            ->getResult()
        ;
    

注意:这样您可以检查检索到的记录的计数并相应地继续。

【讨论】:

如果我错了,请告诉我,但根据您的 sql fiddle,您的查询检索重叠的产品(有预订冲突的产品),而我正在寻找相反的结果。我知道我应该计算记录,如果记录数等于 0,则没有冲突。但是,如果我添加 -&gt;andHaving('COUNT(b) = 0') 我会检索空产品...感谢您与我一起调查! @Benjamin 是的,它是故意这样做的,所以检索到的记录数应该告诉你在给定的时间段内是否有任何预订。我将再次检查教义文档以将其添加到 queryBuilder @Benjamin 我更新了我的答案。我刚刚将 addSelect 更改为-&gt;addSelect('count(b.*) as cnt')。看看这是否可行(因为我不喜欢教义) 你能把count(b.*)改成count(b)吗?这是 Doctrine 的语法错误。 您可以在我的回答中看到我如何处理查询;)感谢您与我一起搜索!【参考方案2】:

我发现使用带有NOT EXISTS () 的子查询而不是内部连接是最好的方法:

SELECT *
FROM product p 
WHERE NOT EXISTS (
  SELECT * FROM booking b WHERE (
    (b.startAt between '2022-01-05 00:00:00' and '2022-01-15 00:00:00') OR 
    (b.endAt between '2022-01-05 00:00:00' and '2022-01-15 00:00:00') OR 
    ('2022-01-05 00:00:00' between b.startAt and b.endAt)
  ) AND b.product_id = p.id
) IN (1,2);

在这里,子查询找到有冲突的预订(感谢@nice_dev in comments)。

学说存储库:

class ProductRepository extends EntityRepository

    private EntityRepository $bookingRepository;

    public function __construct(BookingRepository $bookingRepository)
    
        $this->bookingRepository = $bookingRepository;
    

    public function getAvailableProducts(\DateTimeInterface $startAt, \DateTimeInterface $endAt): array
    
        $bookingQueryBuilder = $this->bookingRepository->createQueryBuilder('b')
            ->andWhere('b.startAt between :startAt and :endAt OR b.endAt between :startAt and :endAt OR :startAt between b.startAt and b.endAt')
            ->andWhere('b.product = p')
        ;

        return $this->createQueryBuilder('p')
            ->andWhere(sprintf('not exists (%s)', $bookingQueryBuilder->getDQL()))
        ;
    

【讨论】:

以上是关于排除学说QueryBuilder中的重叠期的主要内容,如果未能解决你的问题,请参考以下文章

学说:QueryBuilder 存在的地方

学说:QueryBuilder 与 createQuery?

学说 QueryBuilder 返回 QueryException

如何将这个简单的 sql 查询转换为学说 querybuilder

使用 Querybuilder 从全文搜索父路径中排除子路径或从 XPATH/JCR SQL2 查询中获取搜索命中摘录

带日期的 in() 表达式的学说