【问题标题】:Django query annotation with boolean field带有布尔字段的 Django 查询注释
【发布时间】:2015-09-19 01:40:59
【问题描述】:

假设我有一个 Product 模型,其中包含店面中的产品,以及一个带有产品图像的 ProductImages 表,该表可以有零个或多个图像。这是一个简化的示例:

class Product(models.Model):
  product_name = models.CharField(max_length=255)
  # ...

class ProductImage(models.Model):
  product = models.ForeignKey(Product, related_name='images')
  image_file = models.CharField(max_length=255)
  # ...

在显示产品的搜索结果时,我想优先考虑具有相关图像的产品。我可以很容易地得到图片的数量:

from django.db.models import Count
Product.objects.annotate(image_count=Count('images'))

但这实际上并不是我想要的。我想用布尔字段 have_images 对其进行注释,指示产品是否有一张或多张图片,以便我可以按此排序:

Product.objects.annotate(have_images=(?????)).order_by('-have_images', 'product_name')

我该怎么做?谢谢!

【问题讨论】:

标签: python django orm django-queryset


【解决方案1】:

从 Django 1.11 开始,可以使用Exists。下面的例子来自Exists documentation

>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
...     post=OuterRef('pk'),
...     created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))

【讨论】:

  • 我不接受几年前我以前的答案,并接受了这个答案。那时 Django 1.11 还不存在,但肯定没有人应该再使用 Django 1.8。
【解决方案2】:

使用条件表达式并将 outputfield 转换为 BooleanField

Product.objects.annotate(image_count=Count('images')).annotate(has_image=Case(When(image_count=0, then=Value(False)), default=Value(True), output_field=BooleanField())).order_by('-has_image')

【讨论】:

    【解决方案3】:

    当你必须用一些过滤器来注解存在时,可以使用Sum注解。例如,如果images 中有任何 GIF,则进行以下注释:

    Product.objects.filter(
    ).annotate(
        animated_images=Sum(
            Case(
                 When(images__image_file__endswith='gif', then=Value(1)),
                 default=Value(0),
                 output_field=IntegerField()
            )
        )
    )
    

    这实际上会计算它们,但任何 pythonic if product.animated_images: 都会像布尔值一样工作。

    【讨论】:

      【解决方案4】:


      我最终找到了一种使用 django 1.8 的新 conditional expressions 的方法:

      from django.db.models import Case, When, Value, IntegerField
      q = (
          Product.objects
                 .filter(...)
                 .annotate(image_count=Count('images'))
                 .annotate(
                     have_images=Case(
                         When(image_count__gt=0,
                              then=Value(1)),
                         default=Value(0),
                         output_field=IntegerField()))
                 .order_by('-have_images')
      )
      

      这就是我最终找到从 1.7 升级到 1.8 的动力的原因。

      【讨论】:

        【解决方案5】:

        如果性能很重要,我的建议是添加 hasPictures 布尔字段(如 editable=False

        然后通过ProductImagemodel signals(或覆盖savedelete方法)保持正确的值

        优点:

        • 索引友好。
        • 更好的性能。避免连接。
        • 与数据库无关。
        • 对其进行编码将使您的 django 技能更上一层楼。

        【讨论】:

          【解决方案6】:

          阅读有关 extra 的文档

          qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
          

          测试了它的工作原理

          但是 order_bywhere(filter) 这个字段不适合我 (Django 1.8) 0o:

          如果您需要使用一些新的命令来订购生成的查询集 您通过 extra() 包含的字段或表使用 order_by extra() 的参数并传入一个字符串序列。这些字符串 应该是模型字段(如在正常的 order_by() 方法中 查询集),形式为 table_name.column_name 或 a 的别名 您在 extra() 的 select 参数中指定的列。

          qs = qs.extra(order_by = ['-has_images'])
          
          qs = qs.extra(where = ['has_images=1'])
          

          FieldError:无法将关键字“has_images”解析为字段。

          我发现https://code.djangoproject.com/ticket/19434 仍然打开。

          所以如果你和我一样有这样的烦恼,可以使用raw

          【讨论】:

          • 感谢您的回答。我确实找到了a way to do it using django 1.8,没有extra(),绝对没有raw()
          • @BrettGmoser 这就是我喜欢 stackoverflow 的原因:D 总是学习新事物!也谢谢你+1!
          猜你喜欢
          • 2019-09-03
          • 2020-05-14
          • 2018-04-16
          • 2020-05-09
          • 1970-01-01
          • 2021-10-17
          • 1970-01-01
          • 2017-07-25
          • 2020-07-09
          相关资源
          最近更新 更多