【问题标题】:Django: how to annotate queryset with count of filtered ForeignKey field?Django:如何使用过滤的 ForeignKey 字段的计数来注释查询集?
【发布时间】:2012-07-10 05:41:10
【问题描述】:

Django新手问题:)

我有以下模型 - 每个评论都是针对一个产品,每个产品都有一个部门:

class Department(models.Model):
    code = models.CharField(max_length=16)
class Product(models.Model):
    id = models.CharField(max_length=40, primary_key=True, db_index=True)
    dept = models.ForeignKey(Department, null=True, blank=True, db_index=True)
class Review(models.Model):
    review_id = models.CharField(max_length=32, primary_key=True, db_index=True) 
    product = models.ForeignKey(Product, db_index=True) 
    time = models.DateTimeField(db_index=True) 

我想对日期范围(2012-01-01 到 2012-01-08)进行 Django 查询,并返回所有部门的列表,并带有部门 ID 以及该部门的产品数量在该日期范围内进行了审查。

这让我有点头疼:)

我可以获得一个时间范围内的所有评论:

 reviews = Review.filter(time__range=["2012-01-01", "2012-01-08"])

那么我猜每个评论都有一个产品字段,每个产品都有一个部门代码。但是如何按产品和代码、计数和部门 ID 对它们进行分组?

或者,最好请求部门,然后以某种方式用产品数量注释它们?

【问题讨论】:

  • 您不接受我的回答 - 我可以假设它没有正常工作吗?如果是这样,为什么会出错,您是否以另一种方式工作,您能否发布您的解决方案作为未来帮助他人的答案?

标签: python django django-models


【解决方案1】:

尽可能避免使用extrarawaggregation docs 几乎有这个用例:

直接来自文档:

# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
[<Publisher BaloneyPress>, <Publisher SalamiPress>, ...]
>>> pubs[0].num_books
73

所以,要针对您的特定示例进行修改:

depts = Department.objects.
            filter(product__review__time__range=["2012-01-01", "2012-01-08"]).
            annotate(num_products=Count('product'))

在单独的行中调用函数只是为了便于阅读,您应该相应地移动它们。我没有对此进行测试,但我认为它应该可以工作。

【讨论】:

  • 很确定这将是一个 FieldError。无法将关键字“产品”解析为字段。选项有:代码
  • @KrisF 你可能是对的 - 我正在寻找反向关系计数。我必须尝试实际代码才能使其正常工作。
  • 我正在尝试解决这样的问题 - 我很确定原始查询将是必要的。
  • 等等,我很困惑。在我的场景中,我有 CourseProject --> CourseUnit --> Course,类似于 OP 的 Review --> Product --> Department。我可以执行以下查询来获取课程列表,并根据它们包含的 CourseProjects(类似于 OP 的目标)用满足某些条件的 CourseUnits 的数量进行注释: Course.objects.filter(courseunit__courseproject__number__gt=1).annotate(num_units =Count('courseunit')) 这是“获取所有课程并用它们包含的单元数对其进行注释,其中项目的编号 > 1”。 Django 1.5
  • @dshap 现在看来确实有可能。我不确定 13 年 1 月是否有可能。 Django 1.5 还没有发布。也许那时它就成为可能了。我可以发誓我正在试图解决这个问题。也许我忽略了它,或者它是新的。无论哪种方式,请投赞成票。这个答案现在应该被标记为接受了。
【解决方案2】:

在 django &gt;= 2.0 中可以使用

depts = Department.objects.all().annotate(
    num_products=Count('product', filter=Q(product__review__time__range=["2012-01-01", "2012-01-08"]))
)

更多信息请阅读文档https://docs.djangoproject.com/en/2.0/topics/db/aggregation/#filtering-on-annotations

【讨论】:

    【解决方案3】:

    在过去的几天里,我不得不做几个类似的查询,最简单的方法是使用 extra queryset 函数用过滤后的产品计数来注释查询集中的每个对象:

    start = ..  # need to be formatted correctly
    end = ...
    departments = Departments.objects.all().extra(select = {
         'product_count' : """ SELECT COUNT(*) FROM appname_department
                               JOIN appname_product
                                   ON appname_product.dept_id = appname_department.id
                               JOIN appname_review 
                                   ON appname_review.product_id = appname_product.id
                               WHERE appname_review.time BETWEEN %s AND %s
                           """
    }, params=[start, end])
    

    {% for department in departments %}
        {{ department.product_count }}
    {% endfor %}
    

    【讨论】:

    • 此示例易受 SQL 注入攻击
    【解决方案4】:

    聚合文档 https://docs.djangoproject.com/en/dev/topics/db/aggregation/#cheat-sheet

    可能有一种方法可以使用聚合或注释,但我更喜欢这个:

    departments = Department.objects.all()
    for dept in departments : 
        # Get the number of reviewed products for a given range and department
        num_products = dept.product_set.filter(review__time__range=["2012-01-01", "2012-01-08"]).count()
    

    如果您绝对需要它作为模型的功能:

    class Department(models.Model) :
        ...
        def num_products(self, start_date, end_date) : 
            return self.product_set.filter(review__time__range=[start_date, end_date]).count()
    

    编辑

    我认为如果您要进行原始查询(类似这样)

    sql = """SELECT COUNT(Product.*) as num_products, Department.*
        FROM Department
        LEFT OUTER JOIN Product ON Product.department = Department.id
        LEFT OUTER JOIN Review ON Product.id = Review.product
        WHERE Review.time BETWEEN "2012-01-01" AND "2012-01-08"
        GROUP BY Department.id"""
    
    Department.objects.raw(sql)
    

    然后 num_products 将成为结果中每个 Dept 实例的属性。

    您可能需要稍微使用字段 + 表名

    【讨论】:

    • 布莱尔。每个部门一个查询。
    • 好的,我添加了一个只执行一次查询的解决方案
    【解决方案5】:

    我也有类似的数据模型。

    我的解决方案是这样的:

     Department.objects \
    .extra(where=["<review_table_name.time_field> BETWEEN <time1> AND <time2> "])\
    .annotate(num_products=Count('product__review__product_id'))
    

    【讨论】:

      猜你喜欢
      • 2011-03-31
      • 2016-03-16
      • 2021-07-05
      • 2015-11-15
      • 1970-01-01
      • 2019-06-06
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      相关资源
      最近更新 更多