Django- Group by 和 Count by unique together

Posted

技术标签:

【中文标题】Django- Group by 和 Count by unique together【英文标题】:Django- Group by and Count by unique together 【发布时间】:2021-08-01 15:19:41 【问题描述】:

我有以下型号:

class Post(models.Model):
    title = models.CharField(max_length=30)

class PostView(models.Model):
    post = models.ForeignKey(Post, related_name='views', on_delete=models.CASCADE)
    user = models.ForeignKey(get_user_model(), related_name='my_views')
    created = models.DateTimeField(auto_now_add=True)

我想获得按一天中的小时分组且唯一的帖子浏览次数。 例如,如果用户在上午 10 点看到帖子 20 次,则应仅计算一次。 我按以下视图(不是唯一视图)以小时为单位获得帖子:

from django.db.models.functions import TruncHour
from django.db.models import Count

qs = PostView.objects.all().annotate(
        hour=TruncHour('created')
    ).values(
        'hour'
    ).annotate(
        c=Count('id')
    ).values('hour', 'c')

以上代码会将所有视图计算为总视图。我想通过user_idhourpost_id 一起获得独特的观点。 用 ORM 可以做到吗?

【问题讨论】:

我想你正在寻找distinct() 请参阅:docs.djangoproject.com/en/3.2/ref/models/querysets/… 【参考方案1】:

你可以这样做,

from django.db.models import Count

result = PostView.objects.values(
    "created__hour",
    "post",
    "user"
).annotate(count=Count("id"))

print(list(result))

# Result
# ['created__hour': 17, 'post': 1, 'user': 1, 'count': 4, 'created__hour': 17, 'post': 2, 'user': 1, 'count': 3]

【讨论】:

通过在result 之后使用for,我计算了唯一视图并解决了我的问题。但我想知道是不是有什么方法可以完全通过 ORM 来计算这个独特的视图?【参考方案2】:

简答 SQL 和 Django

select a.day_hour, count(*) from (select strftime('%Y-%m-%d %H', created) as day_hour, 
user_id, count(*)  from post_postview 
where post_id=1 group by strftime('%Y-%m-%d %H', created), user_id) 
a group by a.day_hour

Django 答案

In [140]: rs = PostView.objects.filter(post_id=1).extra(
'date_hour': u"strftime('%%Y-%%m-%%d %%H', created)").order_by('date_hour').values('user_id', 'date_hour').annotate(count=Count('user_id', distinct=True))

In [141]: rs
Out[141]: <QuerySet ['date_hour': '2021-05-28 10', 
'user_id': 2, 'count': 1, 'date_hour': '2021-05-28 10', 
'user_id': 3, 'count': 1, 'date_hour': '2021-05-28 11', 
'user_id': 2, 'count': 1, 'date_hour': '2021-05-28 11', 
'user_id': 3, 'count': 1]>

In [142]: rs.values('date_hour').distinct()
Out[142]: <QuerySet ['date_hour': '2021-05-28 10', 
'date_hour': '2021-05-28 11']>

您需要按两次分组。第一次在date_houruser_id 上,第二次在date_hour 上的现有结果集上。

长答案:

由于查询分为两个级别(日期级别和唯一用户),因此您需要两个查询。

在第一步中,您将post_hour 创建的帖子分组。没有这个基本的聚合结果将显示错误的值。

db.sqlite3> select strftime('%Y-%m-%d %H', created) as 
day_hour, user_id, count(*)  from post_postview where 
post_id=1 group by strftime('%Y-%m-%d %H', created), user_id
+---------------+---------+----------+
| day_hour      | user_id | count(*) |
+---------------+---------+----------+
| 2021-05-28 10 | 2       | 1        |
| 2021-05-28 10 | 3       | 2        |
| 2021-05-28 11 | 2       | 3        |
| 2021-05-28 11 | 3       | 2        |
+---------------+---------+----------+

正如你所看到的相同时间间隔(2021-05-28 10), 有2 行。现在要计算这两行,需要额外的查询。

再次通过day_hour 应用同一组,我们每小时得到结果。

select a.day_hour, count(*) from (select strftime('%Y-%m-%d 
%H', created) as day_hour, user_id, count(*)  from 
post_postview where post_id=1 group by strftime('%Y-%m-%d 
%H', created), user_id) a group by a.day_hour;

+---------------+----------+
| day_hour      | count(*) |
+---------------+----------+
| 2021-05-28 10 | 2        |
| 2021-05-28 11 | 2        |
+---------------+----------+

这里我使用了 SQLite 特定的strftime,这是重要的部分。

同样的代码被移植到 Django 中

In [145]: 
PostView.objects.filter(post_id=1).extra('date_hour': 
u"strftime('%%Y-%%m-%%d %%H', 
created)").order_by('date_hour').values('user_id', 
'date_hour').values('date_hour').distinct()
Out[145]: <QuerySet ['date_hour': '2021-05-28 10', 
'date_hour': '2021-05-28 11']>

extra 方法让我们注入 SQL 特定的函数,之后的结果遵循一般的 Django order_bydistinct。 SQLite 不支持 distinct on。

【讨论】:

以上是关于Django- Group by 和 Count by unique together的主要内容,如果未能解决你的问题,请参考以下文章

在 Django 模型上执行 INNER JOIN、GROUP BY 和 COUNT

在 Django 中使用带有 GROUP BY 子句的 COUNT(DISTINCT 字段)

如何选择 COUNT(*) GROUP BY DJANGO? [复制]

Django 做一个简单的 group by/count 语句

如何在 Django ORM 中执行 GROUP BY ... COUNT 或 SUM?

Django ORM,group_by 按所有值分组