Django Window 注释与distinct 子句结合使用

Lud*_*udo 6 django postgresql django-orm

我有一个存储在 Postgres 数据库中的 Django 模型,它由不规则间隔的计数值组成:

WidgetCount
 - Time
 - Count
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用带有 Lag 的窗口函数来为我提供前一行的值作为注释。我的问题是当我尝试将它与一些不同的日期截断结合起来时,窗口函数使用源行而不是明显分组的行。

例如,如果我有以下几行:

time                count
2020-01-20 05:00    15
2020-01-20 06:00    20
2020-01-20 09:00    30
2020-01-21 06:00    35
2020-01-21 07:00    40
2020-01-22 04:00    50
2020-01-22 06:00    54
2020-01-22 09:00    58
Run Code Online (Sandbox Code Playgroud)

我想返回一个显示每天第一次阅读的查询集,我可以使用:

from django.db.models.functions import Trunc

WidgetCount.objects.distinct("date").annotate(date=Trunc("time", "day"))
Run Code Online (Sandbox Code Playgroud)

这给了我:

date        count
01/01/20    15
01/01/21    35
01/01/22    50
Run Code Online (Sandbox Code Playgroud)

我想添加一个注释,它给我昨天的值(这样我就可以显示每天的变化)。

date        count   yesterday_count
01/01/20    15
01/01/21    35      15
01/01/22    50      35
Run Code Online (Sandbox Code Playgroud)

如果我做:

from django.db.models.functions import Trunc, Lag
from django.db.models import Window

WidgetCount.objects.distinct("date").annotate(date=Trunc("time", "day"), yesterday_count=Window(expression=Lag("count")))
Run Code Online (Sandbox Code Playgroud)

第二行返回给我 30 的昨天_计数 - 即,它在应用不同的子句之前向我显示前一行。

如果我添加这样的分区子句:

WidgetCount.objects.distinct("date").annotate(date=Trunc("time", "day"), yesterday_count=Window(expression=Lag("count"), partition_by=F("date")))
Run Code Online (Sandbox Code Playgroud)

然后对于所有行,昨天_计数是无。

如果需要,我可以在 Python 中进行这个计算,但这让我有点生气,我想知道我正在尝试做的事情是否可行。

谢谢!

Ber*_*sen 6

我认为主要问题是,您将注释中使用的操作混合在一起,生成分组查询集(例如 sum),并与简单地为给定查询集中的每个记录创建新字段(例如yesterday_count=Window(expression=Lag("count")).

因此,订购在这里确实很重要。所以当你尝试时:

WidgetCount.objects.distinct("date").annotate(date=Trunc("time", "day"), yesterday_count=Window(expression=Lag("count")))
Run Code Online (Sandbox Code Playgroud)

结果查询集只是 WidgetCount.objects.distinct("date") 注释,不执行分组。

我建议解耦您的操作,以便更容易理解正在发生的事情,并注意到您正在迭代 python 对象,因此不需要进行任何新查询!

请注意使用 SUM 运算作为示例,因为我在使用 FirstValue 运算符时遇到意外错误。所以我用 Sum 发帖来展示仍然相同的想法。对于第一个值,这个想法应该是相同的,只需更改acc_count=Sum("count")first_count=FirstValue("count")

for truncDate_groups in Row.objects.annotate(trunc_date=Trunc('time','day')).values("trunc_date")\
                      .annotate(acc_count=Sum("count")).values("acc_count","trunc_date")\
                      .order_by('trunc_date')\
                      .annotate(y_count=Window(Lag("acc_count")))\
                      .values("trunc_date","acc_count","y_count"):
    print(truncDate_groups)
Run Code Online (Sandbox Code Playgroud)

输出:

{'trunc_date': datetime.datetime(2020, 1, 20, 0, 0, tzinfo=<UTC>), 'acc_count': 65, 'y_count': None}
{'trunc_date': datetime.datetime(2020, 1, 21, 0, 0, tzinfo=<UTC>), 'acc_count': 75, 'y_count': 162}
{'trunc_date': datetime.datetime(2020, 1, 22, 0, 0, tzinfo=<UTC>), 'acc_count': 162, 'y_count': 65}
Run Code Online (Sandbox Code Playgroud)

事实证明 FirstValue 运算符需要使用 Windows 函数,因此您无法嵌套 FirtValue 然后计算 Lag,因此在这种情况下我不确定您是否可以做到这一点。问题是如何在不嵌套窗口的情况下访问 First_Value 列。


Sim*_*tte 0

我还没有在本地测试过它,但我认为你想而GROUP BY不是在这里使用DISTINCT

WidgetCount.objects.values(
    date=Trunc('time', 'day'),
).order_by('date').annotate(
    date_count=Sum('count'),  # Will trigger a GROUP BY date
).annotate(
    yesterday_count=Window(Lag('date_count')),
)
Run Code Online (Sandbox Code Playgroud)