代码之家  ›  专栏  ›  技术社区  ›  Jason Christa

过滤django orm中的聚合

  •  9
  • Jason Christa  · 技术社区  · 15 年前

    我有这样一个函数:

    def post_count(self):
            return self.thread_set.aggregate(num_posts=Count('post'))['num_posts']
    

    我只想统计状态标记为“活动”的帖子。在count函数之前有没有简单的方法添加一个过滤器?

    模型定义:

    class Category(models.Model):
        name = models.CharField(max_length=100)
        slug = models.SlugField(max_length=100, blank=True, primary_key=True)
        ordering = models.IntegerField(max_length=3, default=0)
    
        @property
        def thread_count(self):
            return self.thread_set.all().count()
    
        @property
        def post_count(self):
            return self.thread_set.aggregate(num_posts=Count('post'))['num_posts']
    
    class Thread(models.Model):
        user = models.ForeignKey(User)
        category = models.ForeignKey(Category)
        title = models.CharField(max_length=100)
        slug = models.SlugField(max_length=100)
        content = models.TextField()
        created = models.DateTimeField(auto_now_add=True)
        latest_activity = models.DateTimeField(auto_now_add=True)
    
    class Post(models.Model):
        thread = models.ForeignKey(Thread)
        parent = models.ForeignKey('Post', null=True, blank=True)
        display_name = models.CharField(max_length=100)
        email = models.EmailField(db_index=True)
        ip_address = models.IPAddressField(null=True, blank=True)
        content = models.TextField()
        status = models.CharField(choices=STATUS_CHOICES, max_length=25, db_index=True, default='approved')
        created = models.DateTimeField()
    
    5 回复  |  直到 15 年前
        1
  •  10
  •   Michał Marczyk    15 年前

    好吧,既然问题包括了模型定义,那么我向您提交,这应该是可行的,除非您的Django版本不支持我在这里使用的某些功能(在这种情况下,请告诉我!)以下内容:

    Post.objects.filter(thread__in=thread_set, status='active').aggregate(num_posts=Count('id'))
    

    Django允许 __in 筛选以采用查询集来决定 IN 子句在SQL中应该类似,因此如果通过 thread__in=thread_set ,Django将过滤这些帖子,以便只有那些 thread 字段指向 id 在你的 thread_set 留下来 aggregate 打电话来看。

    这应该用 只有一个数据库查询 有点像 WHERE thread_id IN ... 内部,而不是每个线程一个查询,这确实很可怕。如果发生其他事情,这将是一个臭虫在Django…

    结果应该是 最多两个查询来建立 Category 的邮政编码 --一个获得 线程集 另外一个是计算帖子数。另一种方法是根据筛选线程/后联接 Thread category 字段和 Post status 领域,我不一定期望会更快。 (我说“最多”,因为我想它们可以自动融合…虽然我不认为这会发生在现在的姜戈。不能检查自动取款机,对不起。)

    编辑: Django's QuerySet API reference 说这个开 _在 过滤器:


    在给定的列表中。

    例子:

    Entry.objects.filter(id__in=[1, 3, 4])
    

    SQL等价物:

    SELECT ... WHERE id IN (1, 3, 4);
    

    您还可以使用queryset动态评估值列表,而不是提供文字值列表:

    inner_qs = Blog.objects.filter(name__contains='Cheddar')
    entries = Entry.objects.filter(blog__in=inner_qs)
    

    此查询集将作为subselect语句进行计算:

    SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
    

    上面的代码片段也可以写如下:

    inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
    entries = Entry.objects.filter(blog__in=inner_q)
    

    在Django 1.1中更改: 在Django1.0中,只有后一段代码是有效的。

    第二种形式的可读性稍差,写起来不自然,因为它访问内部查询属性并需要valuesQueryset。如果您的代码不需要与Django1.0兼容,请使用第一个表单,直接传递一个查询集。


    所以,我猜姜戈 能够通过 单一查询 在这里讨论的情况下提交给争议裁决委员会。如果数据库的查询分析器做得很好,效果可能会非常接近最优。:-)

        2
  •  0
  •   jathanism    15 年前

    对。想做就做。这应该按预期工作:

    self.thread_set.filter(active_status=1).aggregate(num_posts=Count('post'))['num_posts']
    

    任何原始查询都返回 QuerySet 如此 any available methods that return QuerySets 对于复杂的条件匹配,可以无限期地链接在一起。自从 aggregate() does not return a QuerySet ,您要确保它是链中的最后一个。

        3
  •  0
  •   Zach    15 年前

    我一直在研究类似的事情,但没有找到一个很好的解决方案。我用的是这样的东西:

    def post_count(self):
            return len(Post.objects.filter(someModel = self).filter(active_status = 1))
    

    这不太好,但我不认为Django允许您根据二级模型聚合和注释进行过滤。我将检查是否有人提出更好的解决方案。

        4
  •  0
  •   Tom Gruff    15 年前

    您可能希望编写自定义管理器对象:

    http://docs.djangoproject.com/en/1.1/topics/db/managers/

    我还没用过 aggregate() ,但这样可以编写一个自定义管理器来提供 active_thread_set 然后做 self.active_thread_set.aggregate(...) .否则,它将允许您执行自定义SQL并添加 num_posts 属性到 Thread 对象(请参见 PollManager.with_counts() 例子。)

        5
  •  0
  •   Mayuresh    15 年前

    可以改变一下吗?

    如下图所示,您可以向线程类添加post-count属性,该属性对线程中的活动日志进行计数。

    然后可以使用此日志计数,通过将一个类别中所有线程中的所有活动日志相加,来计算该类别中的活动日志。

    class Category(models.Model):
        name = models.CharField(max_length=100)
        slug = models.SlugField(max_length=100, blank=True, primary_key=True)
        ordering = models.IntegerField(max_length=3, default=0)
    
        @property
        def thread_count(self):
            return self.thread_set.all().count()
    
        @property
        def post_count(self): # <-- Changed
            return reduce(lambda x,y: x + y, [x.post_count for x in self.thread_set.all()])
    
    class Thread(models.Model):
        user = models.ForeignKey(User)
        category = models.ForeignKey(Category)
        title = models.CharField(max_length=100)
        slug = models.SlugField(max_length=100)
        content = models.TextField()
        created = models.DateTimeField(auto_now_add=True)
        latest_activity = models.DateTimeField(auto_now_add=True)
    
        @property
        def post_count(self): # <---- Newly added
            return self.post_set.filter(status = 'ACTIVE').count()
    
    class Post(models.Model):
        thread = models.ForeignKey(Thread)
        parent = models.ForeignKey('Post', null=True, blank=True)
        display_name = models.CharField(max_length=100)
        email = models.EmailField(db_index=True)
        ip_address = models.IPAddressField(null=True, blank=True)
        content = models.TextField()
        status = models.CharField(choices=STATUS_CHOICES, max_length=25, db_index=True, default='approved')
        created = models.DateTimeField()