Django 注释频率
Django annotate with frequency
(django 3.2.12, python 3.9.3, MySQL 8.0.28)
想象如下模型:
class User(models.Model):
email = models.EmailField(...)
created_datetime = models.DatetimeField(...)
class UserLog(models.Model):
created_datetime = models.DatetimeField(...)
user = models.ForeignKey('user.User' ...)
login = models.BooleanField('Log in' ..)
以及以下查询,旨在用他们的日志频率注释查询集中的每个用户(当 log.login=True
时):
users = User.objects.filter(
Q(...)
).annotate(
login_count=Count('userlog', filter=Q(userlog__login=True)),
login_duration_over=Now() - F('created_datetime'),
login_frequency=ExpressionWrapper(
F('login_duration_over') / F('login_count'),
output_field=models.DurationField()
),
)
这会导致 SQL 错误:
(1064, "You have an error in your SQL syntax;)
生成的 SQL(login_frequency
的片段)如下所示:
(
INTERVAL TIMESTAMPDIFF(
MICROSECOND,
`user_user`.`created_datetime`,
CURRENT_TIMESTAMP
) MICROSECOND / (
COUNT(
CASE WHEN `user_userlog`.`login` THEN `user_userlog`.`id` ELSE NULL END
)
)
) AS `login_frequency`,
和MySQL好像不太喜欢。类似的代码适用于 SQLlite,我在 PG 上被告知。
MySQL 上的 ExpressionWrapper
有什么问题吗?
找到解决方法:
users = User.objects.filter(
Q(...)
).annotate(
login_count=Count('userlog', filter=Q(userlog__login=True)),
login_duration_over=Now() - F('created_datetime'),
login_frequency=Cast(
ExpressionWrapper(
Cast(F('login_duration_over'), output_field=models.BigIntegerField()) / F('login_count'),
output_field=models.BigIntegerField()
),
output_field=models.DurationField()
)
)
这会强制对 bigints 执行 db-side 的 DIVIDE 操作,完成后,将其转换回 timedelta
。
MySQL停止尖叫,结果正确。
尽管如此,但感觉很难看。难道没有更好的办法吗?
(django 3.2.12, python 3.9.3, MySQL 8.0.28)
想象如下模型:
class User(models.Model):
email = models.EmailField(...)
created_datetime = models.DatetimeField(...)
class UserLog(models.Model):
created_datetime = models.DatetimeField(...)
user = models.ForeignKey('user.User' ...)
login = models.BooleanField('Log in' ..)
以及以下查询,旨在用他们的日志频率注释查询集中的每个用户(当 log.login=True
时):
users = User.objects.filter(
Q(...)
).annotate(
login_count=Count('userlog', filter=Q(userlog__login=True)),
login_duration_over=Now() - F('created_datetime'),
login_frequency=ExpressionWrapper(
F('login_duration_over') / F('login_count'),
output_field=models.DurationField()
),
)
这会导致 SQL 错误:
(1064, "You have an error in your SQL syntax;)
生成的 SQL(login_frequency
的片段)如下所示:
(
INTERVAL TIMESTAMPDIFF(
MICROSECOND,
`user_user`.`created_datetime`,
CURRENT_TIMESTAMP
) MICROSECOND / (
COUNT(
CASE WHEN `user_userlog`.`login` THEN `user_userlog`.`id` ELSE NULL END
)
)
) AS `login_frequency`,
和MySQL好像不太喜欢。类似的代码适用于 SQLlite,我在 PG 上被告知。
MySQL 上的 ExpressionWrapper
有什么问题吗?
找到解决方法:
users = User.objects.filter(
Q(...)
).annotate(
login_count=Count('userlog', filter=Q(userlog__login=True)),
login_duration_over=Now() - F('created_datetime'),
login_frequency=Cast(
ExpressionWrapper(
Cast(F('login_duration_over'), output_field=models.BigIntegerField()) / F('login_count'),
output_field=models.BigIntegerField()
),
output_field=models.DurationField()
)
)
这会强制对 bigints 执行 db-side 的 DIVIDE 操作,完成后,将其转换回 timedelta
。
MySQL停止尖叫,结果正确。
尽管如此,但感觉很难看。难道没有更好的办法吗?