Аннотации запросов Django с булевым полем
Скажем, у меня есть модель 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')
Как я могу это сделать? Спасибо!
Ответы
Ответ 1
В итоге я нашел способ сделать это, используя django 1.8 new условные выражения:
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,8 от 1,7.
Ответ 2
Прочитайте документы о extra
qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
Протестировано это работает
Но order_by
или where
(фильтр) этим полем не для меня (Django 1.8) 0o:
Если вам нужно заказать результирующий набор запросов, используя некоторые из новых поля или таблицы, которые вы включили через extra(), используйте order_by параметр extra() и передать последовательность строк. Эти строки должны быть либо полями модели (как в обычном порядке order_by() на querysets), формы table_name.column_name или псевдонима для который вы указали в параметре select для extra().
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError: не удается разрешить ключевое слово has_images в поле.
Я обнаружил, что https://code.djangoproject.com/ticket/19434 все еще открыт.
Итак, если у вас такие проблемы, как я, вы можете использовать raw
Ответ 3
Если производительность имеет значение, мое предложение состоит в том, чтобы добавить hasPictures
логическое поле (как editable=False
)
Затем сохраните правильное значение через ProductImage
сигналы модели (или переписывая методы save
и delete
)
Преимущества:
- Индекс дружественный.
- Лучшая производительность. Избегайте присоединения.
- Агностик базы данных.
- Кодирование поднимет ваши навыки джанго до следующего уровня.
Ответ 4
Когда вам нужно аннотировать существование с некоторыми фильтрами, можно использовать аннотацию 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:
будет работать так же, как и логическая.
Ответ 5
Используйте условные выражения и приведите выходное поле к 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')
Ответ 6
Начиная с Django 1.11 можно использовать Exists
. Пример ниже взят из документации Exists:
>>> 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))