我目前正在尝试查看"空间"在日期范围内,此日期范围可以无限长。 表格如下:
空间:
id available_spaces name 1 20 Space 1 2 40 Space 2 3 10 Space 3
预订(end_date可以为null,这意味着无休止的预订):
id space_id start_date end_date spaces 1 1 13/12-2017 null 9 1 1 13/12-2017 13/12-2018 10
然后我希望能够进行搜索,例如:
from: 11/12-2016 to: null (again meaning endless) spaces: 2
此查询应返回空格:Space 2,Space 3,因为它们都具有该时间间隔的足够可用性。
通过将搜索中所需的空格量更改为1而不是2,应该产生以下结果: 搜索:
from: 11/12-2016 to: null (again meaning endless) spaces: 1
空间1,空间2,空间3。 我发现难以解决的问题是每月可以提供的可变空间量,以及无限量预订的能力。
答案 0 :(得分:1)
首先使用SQL更容易解释查询的工作方式,然后构建SQLAlchemy。我假设预订和搜索总是有一个开头,换句话说,最后只能是无限制的。使用range types和operators,您应首先查找与您的搜索重叠的预订。
select *
from booking
where daterange(start_date, end_date, '[)')
&& daterange('2016-12-11', null, '[)');
从找到的预订中,您需要找到交叉点并总结已用空间。要查找交叉路口,请使用预订的开始并查找包含交叉路口的预订。重复手头的所有预订。例如:
|-------| 5
. . .
. |-------------| 2
. . .
. . |-------------------- 3
. . . .
. . . |---| 1
. . . .
5 7 10 4
并以查询形式:
with bs as (
select *
from booking
where daterange(start_date, end_date, '[)')
&& daterange('2016-12-11', null, '[)')
)
select distinct
b1.space_id,
sum(b2.spaces) as sum
from bs b1
join bs b2
on b1.start_date <@ daterange(b2.start_date, b2.end_date, '[)')
and b1.space_id = b2.space_id
group by b1.id, b1.space_id;
给出了您的示例数据结果
space_id | sum
----------+-----
1 | 19
(1 row)
因为只有2个预订,并且它们具有相同的开始日期。查询远非最佳,并且每个范围必须扫描所有范围,因此至少 O(n ^ 2)。在程序设置中,您可以使用interval tree等进行查找,也可以使用一些合适的索引和更改来改进SQL。
通过相交的预订金额,您可以检查是否存在的余额少于搜索所需的空间:
with bs as (
select *
from booking
where daterange(start_date, end_date, '[)')
&& daterange('2016-12-11', null, '[)')
), cs as (
select distinct
b1.space_id,
sum(b2.spaces) as sum
from bs b1
join bs b2
on b1.start_date <@ daterange(b2.start_date, b2.end_date, '[)')
and b1.space_id = b2.space_id
-- Could also use distinct & sum() over (partition by b1.id) instead
group by b1.id, b1.space_id
)
select *
from space
where not exists(
select 1
from cs
where cs.space_id = space.id
-- Check if there is not enough space
and space.available_spaces - cs.sum < 2
);
从中可以直接形成SQLAlchemy版本:
from functools import partial
from sqlalchemy.dialects.postgresql import DATERANGE
# Hack. Proper type for passing daterange values is
# psycopg2.extras.DateRange, but that does not have
# the comparator methods.
daterange = partial(func.daterange, type_=DATERANGE)
bs = session.query(Booking).\
filter(daterange(Booking.start_date, Booking.end_date, '[)').
overlaps(daterange('2016-12-11', None, '[)'))).\
cte()
bs1 = bs.alias()
bs2 = bs.alias()
cs = session.query(bs1.c.space_id,
func.sum(bs2.c.spaces).label('sum')).\
distinct().\
join(bs2, (bs2.c.space_id == bs1.c.space_id) &
daterange(bs2.c.start_date,
bs2.c.end_date).contains(bs1.c.start_date)).\
group_by(bs1.c.id, bs1.c.space_id).\
cte()
query = session.query(Space).\
filter(~session.query(cs).
filter(cs.c.space_id == Space.id,
Space.available_spaces - cs.c.sum < 2).
exists())