2017-05-03 14 views
8

OuterRef'i (pratik amaçlarla değil, yalnızca çalışmasını sağlamak için) kullanan çok basit bir alt sorgu oluşturmaya çalışıyorum, ancak aynı hatayla çalışmaya devam ediyorum.OuterRef ile basit bir alt sorgu

mesaj/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 kabuk kodu

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

son iki satır bana her Mesaj nesnesi için etiket sayısını vermelidir. Ve burada ben aynı hatayı almaya devam: En örnekle sorunların

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

cevap

17

One .count() çalışır Sorgu Kümesi değerlendirmek ve sayımını dönmek için, çünkü bir alt sorgu olarak queryset.count() kullanamazsınız olmasıdır. Bunun yerine doğru yaklaşımın Count() yerine kullanılması gerektiğini düşünebiliriz. Böyle Belki bir şey:

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

Bu alışkanlık iş iki nedenle: Count tek alanda güvenebilirsiniz ederken

  1. Tag QuerySet, tüm Tag alanları seçer. Böylece: Tag.objects.filter(post=OuterRef('pk')).only('pk')'a ihtiyaç vardır (tag.pk'da saymayı seçmek için).

  2. Count kendisi Count bir Aggregate bir Subquery sınıftır değildir. Yani Count tarafından oluşturulan ifade, Subquery olarak tanınmadı, biz bunu Subquery kullanarak düzeltebiliriz.

Ve son hali şöyle olacaktır:

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

Ancak sorgu

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" 

üretiliyor incelemek eğer biz GROUP BY maddesini sahip olduğunu fark edebilirsiniz. Bunun nedeni, Sayın bir Agrega olmasıdır, şu anda sonucu etkilemez, ancak diğer bazı durumlarda olabilir. docs toplama values + annotate + values

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

belirli bir kombinasyonu yoluyla subquery içine taşınır biraz farklı bir yaklaşım önermek, Thats why Nihayet bu üretecek:

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

Teşekkür ki çalıştı! Ancak, Tag filtresine 'pk__in = [1,2]' eklediğimde, django.core.exceptions.FieldError 'ı alıyorum: İfade karma tipler içeriyor. Output_field 'ayarlamalısınız. – mjuk

+1

“queryset.query” yi yazdırmayı ve karşılığında ne aldığınızı görmek için doğrudan 'RDBMS'nizde çalıştırabilirsiniz. Sanırım bazı satırlar için "Count", 0 yerine "NULL" döndürüyor olabilir. Bunu, satır sonu sayımını, yani .filter (count__gte = 1) 'yi hariç tutmayı onaylayabilirsiniz.Ancak, 'Subquery' ikinci bir argümanı kabul eder, yani 'output_field' 'den şunu deneyebilirsiniz: 'output_field = fields.IntegerField()' – Todor

+0

teşekkürler, tam ihtiyacım olan şey budur. – mjuk