我有一张表,其中一列为
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
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 ORM 来做到这一点,我什至不确定这是否可以用 extra() 巧妙地完成。 Django ORM 在处理日常事务方面非常出色,但对于更复杂的 SQL 语句和需求,尤其是 DBMS 特定的实现,它还不够成熟。您可能必须深入到直接执行原始 SQL,或者将该要求转移到应用程序层来完成。
您始终可以使用 Python 生成缺失的日期,但如果元素的范围和数量很大,那么速度会非常慢。如果 AJAX 请求此用于其他用途(例如图表),那么您可以将其卸载到 Javascript。
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')
您可以使用
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 对于列来说是一个错误的名称(它是数据类型的名称)