SQLAlchemy - 日期范围差距标识,考虑重叠的范围

时间:2016-04-20 15:03:15

标签: sql sqlalchemy gaps-and-islands

我正在尝试在表上执行自联接,并查找与集合(state,office_type,office_class,district)匹配的所有行,以便识别日期范围该集合没有数据。

我当前的查询:

term_alias = aliased(schema.Term, name='term_alias')
query = Session.query(schema.Term, term_alias).filter(schema.Term.office_type_id == term_alias.office_type_id).\
    filter(schema.Term.state_id == term_alias.state_id).\
    filter(schema.Term.office_class == term_alias.office_class).\
    filter(schema.Term.term_end < term_alias.term_begin).\
    filter(or_(schema.Term.district_id == term_alias.district_id,
               schema.Term.district_id == None)).\
    group_by(schema.Term.term_end).\
    group_by(schema.Term.state_id).\
    group_by(schema.Term.office_class).\
    group_by(schema.Term.office_type_id).\
    having(schema.Term.term_end < func.min(term_alias.term_begin)).\
    having((term_alias.term_begin - schema.Term.term_end) > 1)

我得到的结果并不完全有效。

突出显示的行,不应该通过。

enter image description here enter image description here

正如您所看到的,这实际上并不存在差距。第一项,第二项重叠因此不应作为间隙

我在上面的查询中尝试了一些变体,但是我没有得到正确的结果。我的问题是,我如何解释重叠数据?并确保仅报告真正的差距,

print(query)

打印的RAW SQL
SELECT terms.id AS terms_id, terms.term_begin AS terms_term_begin, terms.term_en
d AS terms_term_end, terms.term_served AS terms_term_served, terms.office_type_i
d AS terms_office_type_id, terms.person_id AS terms_person_id, terms.state_id AS
 terms_state_id, terms.district_id AS terms_district_id, terms.removal_reason_id
 AS terms_removal_reason_id, terms.political_party_id AS terms_political_party_i
d, terms.is_elected AS terms_is_elected, terms.is_holdover AS terms_is_holdover,
 terms.neat_race_id AS terms_neat_race_id, terms.office_class AS terms_office_cl
ass, terms.notes AS terms_notes, terms.is_vacant AS terms_is_vacant, term_alias.
id AS term_alias_id, term_alias.term_begin AS term_alias_term_begin, term_alias.
term_end AS term_alias_term_end, term_alias.term_served AS term_alias_term_serve
d, term_alias.office_type_id AS term_alias_office_type_id, term_alias.person_id
AS term_alias_person_id, term_alias.state_id AS term_alias_state_id, term_alias.
district_id AS term_alias_district_id, term_alias.removal_reason_id AS term_alia
s_removal_reason_id, term_alias.political_party_id AS term_alias_political_party
_id, term_alias.is_elected AS term_alias_is_elected, term_alias.is_holdover AS t
erm_alias_is_holdover, term_alias.neat_race_id AS term_alias_neat_race_id, term_
alias.office_class AS term_alias_office_class, term_alias.notes AS term_alias_no
tes, term_alias.is_vacant AS term_alias_is_vacant
FROM terms, terms AS term_alias
WHERE terms.office_type_id = term_alias.office_type_id AND terms.state_id = term
_alias.state_id AND terms.office_class = term_alias.office_class AND terms.term_
end < term_alias.term_begin AND (terms.district_id = term_alias.district_id OR t
erms.district_id IS NULL) GROUP BY terms.term_end, terms.state_id, terms.office_
class, terms.office_type_id
HAVING terms.term_end < min(term_alias.term_begin) AND term_alias.term_begin - t
erms.term_end > :param_1

SQLFiddle的当前状态: http://sqlfiddle.com/#!9/3e030/1

1 个答案:

答案 0 :(得分:1)

您的查询无法处理重叠,可以从简单比较中看出。

查看您的数据,我将使用以下步骤执行搜索:

  1. 查找差距begins:找到所有此类Term Term.term_end未被覆盖的任何其他Term(适当的office_type / office_class / state / district)
  2. 对于每个找到的差距begin,通过查找在差距Term之后开始的最早begin实例来找到差距。
  3. 我参加了以下实施:

    TE = aliased(Term, name='TE')  # gap period start (end of last before gap)
    TO = aliased(Term, name='TO')  # other term for check of TE
    TS = aliased(Term, name='TS')  # start of the next period after gap
    
    # condition for the existance of TE: no other periods overlaping with the next
    # day after TE.term_end
    
    # define an alias for the `TE.term_end + 1 day` (engine specific)
    # gap_sdate = TE.term_end
    gap_sdate = func.date(TE.term_end, text("'+1 days'"))  # sqlite version
    # gap_sdate = func.ADDDATE(TE.term_end, 1))  # mysql version
    
    # subquery which checks if there are Terms covering next day after term_end
    subq = (
        session
        .query(TO.id)
        .filter(and_(
            TE.office_type_id == TO.office_type_id,
            TE.state_id == TO.state_id,
            or_(TE.district_id == TO.district_id,
                and_(TE.district_id == None, TO.district_id == None)
                ),
            TE.office_class == TO.office_class,
        ))
        .filter(TO.id != TE.id)
        .filter(TO.term_begin <= gap_sdate)
        .filter(or_(TO.term_end == None, TO.term_end >= gap_sdate))
    )
    
    # query to find the gaps
    q = (
        session
        .query(
            TE.office_type_id,
            TE.state_id,
            TE.district_id,
            TE.office_class,
            gap_sdate.label("vacant_from"),
            func.date(func.min(TS.term_begin), text("'-1 days'")).label("vacant_till"),  # sqlite version
            # func.ADDDATE(func.min(TS.term_begin), -1).label("vacant_till"),  # mysql version
        )
        .join(TS, and_(
            TE.office_type_id == TS.office_type_id,
            TE.state_id == TS.state_id,
            or_(TE.district_id == TS.district_id,
                and_(TE.district_id == None, TS.district_id == None)
                ),
            TE.office_class == TS.office_class,
        ))
        # filters for gap start
        .filter(TE.term_end != None)
        .filter(~subq.exists())
        # filters for gap end
        .filter(TE.id != TS.id)
        .filter(TS.term_begin > gap_sdate)
        .group_by(TE)
    )
    
    gaps = q.all()
    for gap in gaps:
        print(gap)