如何在Django查询中使用条件注释Count

2024-04-19 10:55:42 发布

您现在位置:Python中文网/ 问答频道 /正文

使用Django ORM,可以做类似于queryset.objects.annotate(Count('queryset_objects', gte=VALUE))的事情。明白我的意思吗?


下面是一个快速示例,用于说明可能的答案:

在Django网站中,内容创建者提交文章,普通用户查看(即阅读)上述文章。文章可以发布(即供所有人阅读),也可以以草稿模式发布。描述这些要求的模型有:

class Article(models.Model):
    author = models.ForeignKey(User)
    published = models.BooleanField(default=False)

class Readership(models.Model):
    reader = models.ForeignKey(User)
    which_article = models.ForeignKey(Article)
    what_time = models.DateTimeField(auto_now_add=True)

我的问题是:如何获取过去30分钟内按独特读者群排序的所有已发表文章?一、 我想计算一下在过去的半个小时里,每一篇发表的文章有多少不同的(独特的)视图,然后根据这些不同的视图生成一个文章列表。


我试过:

date = datetime.now()-timedelta(minutes=30)
articles = Article.objects.filter(published=True).extra(select = {
  "views" : """
  SELECT COUNT(*)
  FROM myapp_readership
    JOIN myapp_article on myapp_readership.which_article_id = myapp_article.id
  WHERE myapp_readership.reader_id = myapp_user.id
  AND myapp_readership.what_time > %s """ % date,
}).order_by("-views")

这引发了错误:语法错误在“01”或附近(其中“01”是extra中的datetime对象)。没什么好说的。


Tags: djangoidmodelobjectsmodelsarticle文章myapp
2条回答

对于django>;=1.8

使用Conditional Aggregation

from django.db.models import Count, Case, When, IntegerField
Article.objects.annotate(
    numviews=Count(Case(
        When(readership__what_time__lt=treshold, then=1),
        output_field=IntegerField(),
    ))
)

说明: 通过文章的常规查询将使用numviews字段进行注释。该字段将被构造为CASE/WHEN表达式,由Count包装,对于readership匹配条件返回1,对于readership not matching条件返回NULL。Count将忽略nulls和Count-only值。

对于最近没有查看过的文章,您将得到0,您可以使用该numviews字段进行排序和筛选。

PostgreSQL的查询将是:

SELECT
    "app_article"."id",
    "app_article"."author",
    "app_article"."published",
    COUNT(
        CASE WHEN "app_readership"."what_time" < 2015-11-18 11:04:00.000000+01:00 THEN 1
        ELSE NULL END
    ) as "numviews"
FROM "app_article" LEFT OUTER JOIN "app_readership"
    ON ("app_article"."id" = "app_readership"."which_article_id")
GROUP BY "app_article"."id", "app_article"."author", "app_article"."published"

如果我们只想跟踪唯一的查询,我们可以在Count中添加distinction,并使我们的When子句返回值,我们希望distinct on。

from django.db.models import Count, Case, When, CharField, F
Article.objects.annotate(
    numviews=Count(Case(
        When(readership__what_time__lt=treshold, then=F('readership__reader')), # it can be also `readership__reader_id`, it doesn't matter
        output_field=CharField(),
    ), distinct=True)
)

这将产生:

SELECT
    "app_article"."id",
    "app_article"."author",
    "app_article"."published",
    COUNT(
        DISTINCT CASE WHEN "app_readership"."what_time" < 2015-11-18 11:04:00.000000+01:00 THEN "app_readership"."reader_id"
        ELSE NULL END
    ) as "numviews"
FROM "app_article" LEFT OUTER JOIN "app_readership"
    ON ("app_article"."id" = "app_readership"."which_article_id")
GROUP BY "app_article"."id", "app_article"."author", "app_article"."published"

对于django<;1.8和PostgreSQL

您可以使用raw来执行由django的较新版本创建的SQL语句。显然,如果不使用raw(即使使用extra注入必需的JOIN子句,也没有简单且优化的方法来查询该数据。

Articles.objects.raw('SELECT'
    '    "app_article"."id",'
    '    "app_article"."author",'
    '    "app_article"."published",'
    '    COUNT('
    '        DISTINCT CASE WHEN "app_readership"."what_time" < 2015-11-18 11:04:00.000000+01:00 THEN "app_readership"."reader_id"'
    '        ELSE NULL END'
    '    ) as "numviews"'
    'FROM "app_article" LEFT OUTER JOIN "app_readership"'
    '    ON ("app_article"."id" = "app_readership"."which_article_id")'
    'GROUP BY "app_article"."id", "app_article"."author", "app_article"."published"')

对于django>;=2.0,可以在聚合函数中使用Conditional aggregation with a ^{} argument

from datetime import timedelta
from django.utils import timezone
from django.db.models import Count, Q # need import

Article.objects.annotate(
    numviews=Count(
        'readership__reader__id', 
        filter=Q(readership__what_time__gt=timezone.now() - timedelta(minutes=30)), 
        distinct=True
    )
)

相关问题 更多 >