我花了最后一天尝试从我的数据库中获取时间序列的聚合。我尝试使用Django ORM,但很快放弃了,然后跑回SQL。我不认为可以使用PSQL generate_series,我认为他们更喜欢你在python中使用itertools或其他方法。
我的模型很像这样:
class Vote(models.Model):
value = models.IntegerField(default=0)
timestamp = models.DateTimeField('date voted', auto_now_add=True)
location = models.ForeignKey('location', on_delete=models.CASCADE)
我想要做的是,随着时间的推移显示一系列指标 - 目前,当前用户每小时的当前聚合。用户设置了时区(默认为' America / Chicago')。我一直在使用postgres查询,插入大量的AS TIME ZONE强制转换以争取边界并返回查询的值。我让它在昨晚深夜返回了正确的结果,但今天早上,它再次关闭。我知道我做的事情非常愚蠢。我甚至使用了双重时间戳,因为Postgres处理AT TIME ZONE的方式很奇怪(纠正到UTC而不是FROM)
同样,我想在用户当前每天的每个小时显示汇总数据,直到/现在包括#。
这是我目前的查询:
WITH hour_intervals AS (
SELECT * FROM generate_series(date_trunc('day',(SELECT TIMESTAMP 'today' AT TIME ZONE 'UTC' AT TIME ZONE %s)), (LOCALTIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE %s), '1 hour') start_time
)
SELECT f.start_time,
COUNT(id) total,
COUNT(CASE WHEN value > 0 THEN 1 END) AS positive_votes,
COUNT(CASE WHEN value = 0 THEN 1 END) AS indifferent_votes,
COUNT(CASE WHEN value < 0 THEN 1 END) AS negative_votes,
SUM(CASE WHEN value > 0 THEN 2 WHEN value = 0 THEN 1 WHEN value < 0 THEN -4 END) AS score
FROM votes_vote m
RIGHT JOIN hour_intervals f
ON m.timestamp AT TIME ZONE %s >= f.start_time AND m.timestamp AT TIME ZONE %s < f.start_time + '1 hour'::interval
AND m.location_id = %s
GROUP BY f.start_time
ORDER BY f.start_time
调试信息
Django 1.9.2
和我的settings.py有USE_TZ=True
Postgres 9.5.2
和django的登录角色
ALTER ROLE yesno_django
SET client_encoding = 'utf8';
ALTER ROLE yesno_django
SET default_transaction_isolation = 'read committed';
ALTER ROLE yesno_django
SET TimeZone = 'UTC';
更新 稍微摆弄一下查询,现在这是今天投票的一个有效查询......
WITH hour_intervals AS (
SELECT * FROM generate_series((SELECT TIMESTAMP 'today' AT TIME ZONE 'UTC'), (LOCALTIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE %s), '1 hour') start_time
)
SELECT f.start_time,
COUNT(id) total,
COUNT(CASE WHEN value > 0 THEN 1 END) AS positive_votes,
COUNT(CASE WHEN value = 0 THEN 1 END) AS indifferent_votes,
COUNT(CASE WHEN value < 0 THEN 1 END) AS negative_votes,
SUM(CASE WHEN value > 0 THEN 2 WHEN value = 0 THEN 1 WHEN value < 0 THEN -4 END) AS score
FROM votes_vote m
RIGHT JOIN hour_intervals f
ON m.timestamp AT TIME ZONE %s >= f.start_time AND m.timestamp AT TIME ZONE %s < f.start_time + '1 hour'::interval
AND m.location_id = %s
GROUP BY f.start_time
ORDER BY f.start_time
为什么我之前的查询在昨晚7点到10点之间工作得很好但是今天失败了?我是否应该期望这个新查询也会下降?
有人可以解释我第一次(或每次)出错的地方吗?
答案 0 :(得分:1)
首先,将related_name='votes'
添加到您的外键到位置,以便更好地控制,现在使用您可以执行的位置模型:
from django.db.models import Count, Case, Sum, When, IntegerField
from django.db.models.expressions import DateTime
queryset = location.objects.annotate(
datetimes=DateTime('votes__timestamp', 'hour', tz),
positive_votes=Count(Case(
When(votes__value__gt=0, then=1),
default=None,
output_field=IntegerField())),
indifferent_votes=Count(Case(
When(votes__value=0, then=1),
default=None,
output_field=IntegerField())),
negative_votes=Count(Case(
When(votes__value__lt=0, then=1),
default=None,
output_field=IntegerField())),
score=Sum(Case(
When(votes__value__lt=0, then=-4),
When(votes__value=0, then=1),
When(votes__value__gt=0, then=2),
output_field=IntegerField())),
).values_list('datetimes', 'positive_votes', 'indifferent_votes', 'negative_votes', 'score').distinct().order_by('datetimes')
这将为每个位置生成统计信息。您当然可以将其过滤到任何位置或时间范围。
答案 1 :(得分:0)
如果您正在处理的日期时间字段将允许空值,则可以使用以下内容解决https://code.djangoproject.com/ticket/25937:
Potato.objects.annotate(
time=Coalesce(
TruncMonth('removed', tzinfo=timezone.UTC()),
Value(datetime.min.replace(tzinfo=timezone.UTC()),
).values('time').annotate(c=Count('pk'))
这将使用易于发现的哨兵替换NULL时间。如果您已经在使用datetime.min
,那么您将不得不想出其他的东西。
我在制作中使用了这个,但是我发现它上面的TruncMonth()
会给你当地时间,当你把Coalesce()
放在它周围时你可以只有天真或UTC。