排除学说QueryBuilder中的重叠期
Posted
技术标签:
【中文标题】排除学说QueryBuilder中的重叠期【英文标题】:Excluding overlapped period in doctrine QueryBuilder 【发布时间】:2021-10-25 15:57:36 【问题描述】:我有Product
和Booking
实体,我正在尝试选择在给定时间段内尚未预订的产品。换句话说,在给定时间段内选择可用的产品。
为了便于理解,请参阅下面的架构。我只想选择具有 #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,则没有冲突。但是,如果我添加->andHaving('COUNT(b) = 0')
我会检索空产品...感谢您与我一起调查!
@Benjamin 是的,它是故意这样做的,所以检索到的记录数应该告诉你在给定的时间段内是否有任何预订。我将再次检查教义文档以将其添加到 queryBuilder
@Benjamin 我更新了我的答案。我刚刚将 addSelect 更改为->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 与 createQuery?
学说 QueryBuilder 返回 QueryException
如何将这个简单的 sql 查询转换为学说 querybuilder