【问题标题】:Excluding overlapped period in doctrine QueryBuilder排除学说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'] 存在预订,则该产品不可用。所以是的,这个查询的结果是正确的,但它不是正确的查询。我正在寻找一个仅输出可用产品的查询,但我在执行此操作时遇到了困难。

标签: php mysql sql doctrine period


【解决方案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()))
        ;
    }
}

【讨论】:

    猜你喜欢
    • 2016-08-25
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    相关资源
    最近更新 更多