如何在SQLAlchemy或SQL的间隔交集中查找与月份相匹配的条目?

时间:2017-08-03 14:37:48

标签: python sql postgresql sqlalchemy

我的一位用户希望回答这个问题:

“在2005年至2010年的任何一个月中,有哪些要求存在?”

数据库表“request”有2列,start_dateend_date,代表请求生命周期的间隔。

SQLAlchemy模型如下所示:

class Request(SomeBaseModel):
    ...
    start_date = db.Column(db.Date, default=date.today)
    end_date = db.Column(db.Date, default=in_one_year)

然后我有一个在Python中动态获得5年零一个月的过程:

`initial_date`, five_years_later, month_number = getTimePeriod()

根据这些参数,我必须列出initial_datefive_years_later之间开始或结束的所有请求。通过将start_dateend_dateinitial_datefive_years_later进行比较,我可以轻松完成这项工作。

然而,困难的部分是仅获取在特定月份内存在的请求,而该月份也是(initial_datefive_years_later)间隔的一部分。规则是:

  • 请求可能在本月之前和之后存在,但月份必须在其生命周期内。
  • 月份可以在请求生命周期中出现多次,但不能出现0次。
  • 完全相同的月份必须出现在一个请求有效期和(initial_datefive_years_later)间隔内。

我可以通过为(initial_datefive_years_later)间隔的每一年生成每个月的开始日期和结束日期来做到这一点,然后检查这些对中的任何一对是否与请求重叠一生:

        filters = []
        for year in range(initial_date, five_years_later + 1):
            month_start_date = datetime(year, month, 1)
            month_end_date = datetime(year, month, calendar.mdays[month])
            filters.append(
                (requeest.start_date <= month_end_date) &
                (request.end_date >= month_start_date)
            )
        is_active = functools.reduce(operator.or_, filters)
        auth_requests = auth_requests.filter(is_active)

然而,我的胆量告诉我有更好的方法。

SQLAlchemy查询将是最好的答案,但Postgres的SQL版本可以。

2 个答案:

答案 0 :(得分:2)

以下是我解决问题的方法。

无论哪种   - 请求的持续时间应大于等于1年。

或者   - start_date必须在三月期间或之后> end_date应在三月或之前。

AND

start_date / end_date必须在范围内(2005,2010)

此postgres查询将检查条件:

select * from request
where 1 = 
CASE
    WHEN extract(year from age(end_date,start_date)) >= 1 THEN 1
    WHEN (extract(month from start_date)::integer <= 3 
             AND extract(month from end_date)::integer >= 3 )
         AND extract(year from age(end_date,start_date)) < 1 THEN 1
    WHEN (extract(month from start_date)::integer >= 3 
             AND extract(month from end_date)::integer <= 3 )
         AND extract(year from age(end_date,start_date)) < 1 THEN 1
    ELSE 0
END
AND ((extract(year from start_date)::integer >= 2005
and  extract(year from start_date)::integer <= 2010)
OR (extract(year from end_date)::integer >= 2005
and  extract(year from end_date)::integer <= 2010))
;

编辑:这比我第一次意识到的要复杂得多。编辑查询以满足所有条件。

答案 1 :(得分:1)

实际上,您在问题中的建议构建了一组过滤器,用于评估开始日期和结束日期与相关月份之间的交集。因此,您将月份,开始和结束日期转换为大约五个过滤器(取决于边界条件),然后使用或运算符。 假设您的列已正确编入索引(或者如果不是全表扫描是您的数据的正确答案),我认为任何事情都不会比这更好。您的查询为Postgres提供了一组要比较的时间间隔。每行最多需要处理一次。 对于这个理想的问题。 所以我的答案是你已经找到了最好的方法。可能有其他方法具有相同的性能特征,但您所拥有的内容很容易理解并且表现良好。