排序有时在我的查询中不起作用

时间:2018-07-12 02:14:13

标签: python sql sqlalchemy

我定义了两个类:

class OrderEntryVacancyRenew(OrderEntry):
    ...
    vacancy_id = db.Column(db.Integer, db.ForeignKey('vacancy.id'), nullable=False)
    vacancy = db.relationship('Vacancy')
    remaining = db.Column(db.SmallInteger)

class Vacancy(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    renew_at = db.Column(TZDateTime, index=True)

然后,我定义了刷新OrderEntryVacancyRenew.remainingVacancy.renew_at的方法。

def renew_vacancy():
    filters = [
        OrderEntryVacancyRenew.remaining,
        Vacancy.status == 0,
        or_(
            Vacancy.renew_at <= (utcnow() - INTERVAL),
            Vacancy.renew_at.is_(None))
    ]

    renew_vacancies = OrderEntryVacancyRenew.query.options(
        load_only('remaining', 'vacancy_id')
    ).order_by(
        OrderEntryVacancyRenew.id
    ).from_self().group_by(
        OrderEntryVacancyRenew.vacancy_id
    ).join(
        OrderEntryVacancyRenew.vacancy
    ).options(
        contains_eager(OrderEntryVacancyRenew.vacancy).load_only('renew_at')
    ).filter(*filters)

    for entry in renew_vacancies:
        entry.vacancy.renew_at = utcnow()
        entry.remaining -= 1

    db.session.commit()

我写了单元测试来检查renew_vacancy

vacancy1 = Vacancy(id=10000)
vacancy2 = Vacancy(id=10001)
db.session.add_all([vacancy1, vacancy2])
vacancy_renew1 = OrderEntryVacancyRenew(
    vacancy_id=vacancy1.id,
    remaining=24)
# make sure vacancy_renew1.id < vacancy_renew2.id
db.session.add(vacancy_renew1)
db.session.commit()
vacancy_renew2 = OrderEntryVacancyRenew(
    vacancy_id=vacancy1.id,
    remaining=8)
vacancy_renew3 = OrderEntryVacancyRenew(
    vacancy_id=vacancy2.id,
    remaining=42)
db.session.add_all((vacancy_renew2, vacancy_renew3))
db.session.commit()

renew_vacancy()
self.assertEqual(
    (vacancy_renew1.remaining, vacancy_renew2.remaining), (23, 8))

renew_vacancies由OrderEntryVacancyRenew ID排序,而空缺ID分组,所以我希望它会过滤vacancy_renew1vacancy_renew3

我使用以下命令来运行单元测试100次:

for i in `seq 1 100`; do python test.py; done

在极少数情况下,它过滤vacancy_renew2而不是vacancy_renew1enter image description here enter image description here

为什么有时order by不能按预期工作?

我尝试在vacancy_renew1.id之后打印vacancy_renew2.idrenew_vacancy

...
db.session.commit()
renew_vacancy()
print vacancy_renew1.id
print vacancy_renew2.id
self.assertEqual(
    (vacancy_renew1.remaining, vacancy_renew2.remaining), (23, 8))
...

enter image description here

1 个答案:

答案 0 :(得分:0)

  

为什么有时ORDER BY无法按预期工作?

考虑到标准SQL,查询的结果是不确定的,因此知道为什么它在大多数时间都有效并且很少失败并不十分有价值。有两点使结果有所不同:

  1. 通常you should not rely on the order of rows of a subquery用于封闭查询,即使您应用排序也是如此。某些数据库实现可能具有其他保证,但是在其他一些实现上,例如optimizers may deem the ORDER BY unnecessary – MySQL 5.7及更高版本可以保证完全删除子查询。

  2. 通常使用
  3. SQLite和MySQL 1 这样的数据库,这些数据库允许选择不在functionally dependent上或未在GROUP BY子句中命名的非聚合项,不用指定要从组中的哪一行获取值:

    • SQLite

        

      ...否则,将根据组中任意选择的一行进行评估。如果结果集中有多个以上非聚集表达式,则将针对同一行对所有此类表达式求值。

    • MySQL

        

      ...在这种情况下,服务器可以从每个组中自由选择任何值,因此,除非它们相同,否则选择的值是不确定的,这可能不是您想要的。此外,通过添加ORDER BY子句不会影响从每个组中选择值。

在SQLite上尝试查询失败使此机器上的声明失败,而在MySQL上则通过了。这可能是由于实现了从组中选择行的结果,但您不应依赖这些细节。

您似乎想要的是一个查询或top-1。不知道您正在使用哪个数据库,这是一种使用EXISTS子查询表达式来完成的通用方法:

renew_alias = db.aliased(OrderEntryVacancyRenew)

renew_vacancies = db.session.query(OrderEntryVacancyRenew).\
    join(OrderEntryVacancyRenew.vacancy).\
    options(
        load_only('remaining'),
        contains_eager(OrderEntryVacancyRenew.vacancy).load_only('renew_at')).\
    filter(db.not_(db.exists().where(
        db.and_(renew_alias.vacancy_id == OrderEntryVacancyRenew.vacancy_id,
                renew_alias.id < OrderEntryVacancyRenew.id)))).\
    filter(*filters)

此查询在SQLite和MySQL上均传递断言。另外,您也可以用LEFT JOIN和IS NULL谓词替换EXISTS子查询表达式。

Ps。。我想您正在使用某种MySQL版本,并遵循this之类的建议。您还应该阅读有关该评论的注释,因为有很多人正确地指出了陷阱。它至少在MySQL 5.7及更高版本上不起作用。


1 :可通过ONLY_FULL_GROUP_BY SQL模式设置控制,默认情况下在MySQL 5.7.5及更高版本中启用。