簡體   English   中英

如何使用Django查詢集中的條件注釋Count

[英]How to annotate Count with a condition in a Django queryset

使用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”是額外的日期時間對象)。 繼續下去並不多。

對於django> = 1.8

使用條件聚合

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包裝,對於讀者匹配標准將返回1,對於不匹配標准的讀者將返回NULL Count將忽略空值並僅計算值。

您將在最近未查看的文章上獲得零,並且您可以使用該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添加區別,並使我們的When子句返回值,我們希望區別開來。

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,您可以在聚合函數中使用帶有filter參數的條件聚合

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
    )
)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM