排除学说QueryBuilder中的重叠时期
Excluding overlapped period in doctrine QueryBuilder
我有 Product
和 Booking
实体,我正在尝试 select 在给定时间段内尚未预订的产品。换句话说,在给定期间 select 可用产品。
为了便于理解,请参阅下面的架构。我只想要 select 有像 #1 或 #5 这样的预订的产品,因为它是可用的。如果某个产品有一些预订,例如 #2、#3、#4、#6,请不要 select 因为它不可用。
每个 |--|
代表一个句点,左边是 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时,数据库(MYSQL5.6)select这个产品就算#2,#3 , #4 或 #6 存在。
我对这个查询有点困惑,我觉得我走错了路,所以我们将不胜感激!
产品may/may在给定时间段内没有预订。要检测重叠或碰撞,您最多需要检查 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);
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()
;
}
}
注意: 这样您可以检查检索到的记录的 计数 并相应地继续。
我发现使用带有 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)。
学说存储库:
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()))
;
}
}
我有 Product
和 Booking
实体,我正在尝试 select 在给定时间段内尚未预订的产品。换句话说,在给定期间 select 可用产品。
为了便于理解,请参阅下面的架构。我只想要 select 有像 #1 或 #5 这样的预订的产品,因为它是可用的。如果某个产品有一些预订,例如 #2、#3、#4、#6,请不要 select 因为它不可用。
每个 |--|
代表一个句点,左边是 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时,数据库(MYSQL5.6)select这个产品就算#2,#3 , #4 或 #6 存在。
我对这个查询有点困惑,我觉得我走错了路,所以我们将不胜感激!
产品may/may在给定时间段内没有预订。要检测重叠或碰撞,您最多需要检查 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);
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()
;
}
}
注意: 这样您可以检查检索到的记录的 计数 并相应地继续。
我发现使用带有 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);
在这里,子查询找到了冲突的预订(感谢
学说存储库:
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()))
;
}
}