2017-05-03 30 views
8

我試圖做一個使用OuterRef的非常簡單的子查詢(不是爲了實際目的,只是爲了讓它工作),而是一直運行到相同的錯誤。使用OuterRef的簡單子查詢

帖子/ models.py

from django.db import models 

class Tag(models.Model): 
    name = models.CharField(max_length=120) 
    def __str__(self): 
     return self.name 

class Post(models.Model): 
    title = models.CharField(max_length=120) 
    tags = models.ManyToManyField(Tag) 
    def __str__(self): 
     return self.title 

manage.py shell代碼

>>> from django.db.models import OuterRef, Subquery 
>>> from posts.models import Tag, Post 
>>> tag1 = Tag.objects.create(name='tag1') 
>>> post1 = Post.objects.create(title='post1') 
>>> post1.tags.add(tag1) 
>>> Tag.objects.filter(post=post1.pk) 
<QuerySet [<Tag: tag1>]> 
>>> tags_list = Tag.objects.filter(post=OuterRef('pk')) 
>>> Post.objects.annotate(count=Subquery(tags_list.count())) 

最後兩行應該給我的每一個Post對象的標籤數量。在這裏,我不斷收到同樣的錯誤:

ValueError: This queryset contains a reference to an outer query and may only be used in a subquery. 

回答

17

一個與你的榜樣的問題是,你不能使用queryset.count()作爲一個子查詢,因爲.count()試圖評估查詢集並返回計數。

所以人們可能會認爲正確的做法是使用Count()來代替。也許是這樣的:

Post.objects.annotate(
    count=Count(Tag.objects.filter(post=OuterRef('pk'))) 
) 

這不會工作,原因有二:

  1. Tag查詢集選擇所有Tag領域,而Count只能在一個場數。因此:需要Tag.objects.filter(post=OuterRef('pk')).only('pk')(選擇tag.pk的計數)。

  2. Count本身不是Subquery類,CountAggregate。所以Count生成的表達式不被識別爲Subquery,我們可以通過使用Subquery來解決這個問題。

,正式版本將是:

Post.objects.annotate(
    count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk'))) 
) 

然而 如果您檢查正在生產

SELECT 
    "tests_post"."id", 
    "tests_post"."title", 
    COUNT((SELECT U0."id" 
      FROM "tests_tag" U0 
      INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") 
      WHERE U1."post_id" = ("tests_post"."id")) 
    ) AS "count" 
FROM "tests_post" 
GROUP BY 
    "tests_post"."id", 
    "tests_post"."title" 

查詢您可能會注意到我們有一個GROUP BY條款。這是因爲Count是一個Aggregate,現在它不會影響結果,但在某些情況下它可能會影響結果。這就是爲什麼docs建議一點點不同的方法,其中聚合是通過的values + annotate + values

Post.objects.annotate(
    count=Subquery(
     Tag.objects.filter(post=OuterRef('pk')) 
      .values('post') 
      .annotate(count=Count('pk')) 
      .values('count') 
    ) 
) 

的特定組合搬進subquery最後這將產生:

SELECT 
    "tests_post"."id", 
    "tests_post"."title", 
    (SELECT COUNT(U0."id") AS "count" 
      FROM "tests_tag" U0 
      INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") 
      WHERE U1."post_id" = ("tests_post"."id") 
      GROUP BY U1."post_id" 
    ) AS "count" 
FROM "tests_post" 
+0

謝謝,成功了!但是,當我將'pk__in = [1,2]'添加到標記過濾器時,我得到'django.core.exceptions.FieldError:表達式包含混合類型。你必須設置output_field'。 – mjuk

+1

您可以嘗試打印'queryset.query'並直接在您的'RDBMS'中執行以查看您獲得的回報。我猜測對於某些行,Count可能返回NULL而不是0.您可以嘗試通過臨時排除無數行的行來確認,即'.filter(count__gte = 1)'。然而,'Subquery'接受第二個參數,它是'output_field',你可以嘗試將它設置爲:'output_field = fields.IntegerField()' – Todor

+0

謝謝,這是我所需要的。 – mjuk