带有布尔字段的 Django 查询注释
Django query annotation with boolean field
假设我有一个 Product
模型,在店面中有产品,还有一个 ProductImages
table 有产品图片,可以有零个或多个图片。这是一个简化的例子:
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')
我该怎么做?谢谢!
阅读有关 extra
的文档
qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
测试有效
但是 order_by
或 where
(filter) 这个字段不适合我 (Django 1.8) 0o:
If you need to order the resulting queryset using some of the new
fields or tables you have included via extra() use the order_by
parameter to extra() and pass in a sequence of strings. These strings
should either be model fields (as in the normal order_by() method on
querysets), of the form table_name.column_name or an alias for a
column that you specified in the select parameter to extra().
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError: Cannot resolve keyword 'has_images' into field.
我发现 https://code.djangoproject.com/ticket/19434 仍然打开。
所以如果你和我一样有这样的困扰,可以使用raw
如果性能很重要,我的建议是添加 hasPictures
布尔字段(如 editable=False
)
然后通过ProductImage
model signals(或覆盖save
和delete
方法)
保留正确的值
优点:
- 索引友好。
- 更好的性能。避免连接。
- 数据库不可知。
- 编写代码将使您的 Django 技能更上一层楼。
我最终找到了一种方法来使用 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 的动力的原因。
当你必须用一些过滤器来注释存在时,可以使用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:
都将像布尔值一样工作。
使用条件表达式并将输出字段转换为布尔字段
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')
从 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))
假设我有一个 Product
模型,在店面中有产品,还有一个 ProductImages
table 有产品图片,可以有零个或多个图片。这是一个简化的例子:
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')
我该怎么做?谢谢!
阅读有关 extra
的文档qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
测试有效
但是 order_by
或 where
(filter) 这个字段不适合我 (Django 1.8) 0o:
If you need to order the resulting queryset using some of the new fields or tables you have included via extra() use the order_by parameter to extra() and pass in a sequence of strings. These strings should either be model fields (as in the normal order_by() method on querysets), of the form table_name.column_name or an alias for a column that you specified in the select parameter to extra().
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError: Cannot resolve keyword 'has_images' into field.
我发现 https://code.djangoproject.com/ticket/19434 仍然打开。
所以如果你和我一样有这样的困扰,可以使用raw
如果性能很重要,我的建议是添加 hasPictures
布尔字段(如 editable=False
)
然后通过ProductImage
model signals(或覆盖save
和delete
方法)
优点:
- 索引友好。
- 更好的性能。避免连接。
- 数据库不可知。
- 编写代码将使您的 Django 技能更上一层楼。
我最终找到了一种方法来使用 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 的动力的原因。
当你必须用一些过滤器来注释存在时,可以使用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:
都将像布尔值一样工作。
使用条件表达式并将输出字段转换为布尔字段
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')
从 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))