我定义了两个类:
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.remaining
和Vacancy.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_renew1
和vacancy_renew3
。
我使用以下命令来运行单元测试100次:
for i in `seq 1 100`; do python test.py; done
在极少数情况下,它过滤vacancy_renew2
而不是vacancy_renew1
。
为什么有时order by
不能按预期工作?
我尝试在vacancy_renew1.id
之后打印vacancy_renew2.id
和renew_vacancy
。
...
db.session.commit()
renew_vacancy()
print vacancy_renew1.id
print vacancy_renew2.id
self.assertEqual(
(vacancy_renew1.remaining, vacancy_renew2.remaining), (23, 8))
...
答案 0 :(得分:0)
为什么有时ORDER BY无法按预期工作?
考虑到标准SQL,查询的结果是不确定的,因此知道为什么它在大多数时间都有效并且很少失败并不十分有价值。有两点使结果有所不同:
通常you should not rely on the order of rows of a subquery用于封闭查询,即使您应用排序也是如此。某些数据库实现可能具有其他保证,但是在其他一些实现上,例如optimizers may deem the ORDER BY unnecessary – MySQL 5.7及更高版本可以保证完全删除子查询。
SQLite和MySQL 1 这样的数据库,这些数据库允许选择不在functionally dependent上或未在GROUP BY子句中命名的非聚合项,不用指定要从组中的哪一行获取值:
在SQLite上尝试查询失败使此机器上的声明失败,而在MySQL上则通过了。这可能是由于实现了从组中选择行的结果,但您不应依赖这些细节。
您似乎想要的是一个greatest-n-per-group查询或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及更高版本中启用。