Django + PostgreSQL:填充范围内缺失的日期

问题描述 投票:0回答:3

我有一张表,其中一列为

date
。每个日期可以有多个条目。

date         .....
-----------  -----
2015-07-20     ..
2015-07-20     ..
2015-07-23     ..
2015-07-24     ..

我想使用 Django ORM 和 PostgreSQL 作为数据库后端来获取以下形式的数据:

date         count(date)
-----------  -----------
2015-07-20        2
2015-07-21        0       (missing after aggregation)
2015-07-22        0       (missing after aggregation)
2015-07-23        1
2015-07-24        1

对应的PostgreSQL查询:

WITH RECURSIVE date_view(start_date, end_date) 
AS ( VALUES ('2015-07-20'::date, '2015-07-24'::date) 
     UNION ALL SELECT start_date::date + 1, end_date 
     FROM date_view 
     WHERE start_date < end_date ) 
SELECT start_date, count(date) 
FROM date_view LEFT JOIN my_table ON date=start_date 
GROUP BY date, start_date 
ORDER BY start_date ASC;

我无法将此原始查询转换为 Django ORM 查询。

如果有人可以提供一个示例 ORM 查询,无论是否有使用 PostgreSQL 作为数据库后端的公用表表达式的解决方法,那就太好了。

简单的原因引用这里

我的偏好是在数据库中进行尽可能多的数据处理,而不是真正涉及的演示内容。我不羡慕在应用程序代码中执行此操作,只要这是一次数据库访问即可

根据这个答案 django 本身不支持 CTE,但答案似乎相当过时。

参考资料:

谢谢

django postgresql django-queryset django-orm django-1.8
3个回答
2
投票

我不认为你可以用纯 Django ORM 来做到这一点,我什至不确定这是否可以用 extra() 巧妙地完成。 Django ORM 在处理日常事务方面非常出色,但对于更复杂的 SQL 语句和需求,尤其是 DBMS 特定的实现,它还不够成熟。您可能必须深入到直接执行原始 SQL,或者将该要求转移到应用程序层来完成。

您始终可以使用 Python 生成缺失的日期,但如果元素的范围和数量很大,那么速度会非常慢。如果 AJAX 请求此用于其他用途(例如图表),那么您可以将其卸载到 Javascript。


0
投票
from datetime import date, timedelta
from django.db.models.functions import Trunc
from django.db.models.expressions import Value
from django.db.models import Count, DateField

# A is model

start_date = date(2022, 5, 1)
end_date = date(2022, 5, 10)

queryset_days = A.objects\
    .annotate(date=Trunc('created', 'day', output_field=DateField())) \
    .filter(date__gte=start_date, date__lte=end_date) \
    .values('date')\
    .annotate(count=Count('id'))

queryset_missed_days = A.objects\
     .extra(select={
          'created': 'unnest(Array[%s]::date[])' %
          ','.join(map(lambda d: "'%s'::date" % d.strftime('%Y-%m-%d'),
                       set(start_date + timedelta(n)
                           for n in range((end_date - start_date).days + 1)) -
                       set(queryset_days.values_list('date', flat=True))))})\
     .annotate(count=Value(0))\
     .values('created', 'count'))

result = queryset_days.union(queryset_missed_days).order_by('date')


-2
投票

您可以使用

generate_series()
来代替递归 CTE 来构造日历表:

SELECT calendar, count(mt.zdate) as THE_COUNT
FROM generate_series('2015-07-20'::date
                   , '2015-07-24'::date
                   , '1 day'::interval)  calendar
LEFT JOIN my_table mt ON mt.zdate = calendar
GROUP BY 1
ORDER BY 1 ASC;

顺便说一句:我将

date
重命名为
zdate
。 DATE 对于列来说是一个错误的名称(它是数据类型的名称)

© www.soinside.com 2019 - 2024. All rights reserved.