Аннотировать значение последнего, связанного с Django 1.8, используя условную аннотацию
У меня есть следующие модели:
class City(models.Model):
...
class Census(models.Model):
city = models.ForeignKey(City)
date = models.DateTimeField()
value = models.BigIntegerField()
Теперь я хотел бы аннотировать параметр City-queryset со значением последней переписи. Как мне это достичь?
Я пробовал:
City.objects.annotate(population=Max('census__date'))
# --> annotates date and not value
City.objects.annotate(population=Max('census__value'))
# --> annotates highest value, not latest
City.objects.annotate(population=
Case(
When(
census__date=Max('census__date'),
then='census__value')
)
)
# --> annotates 'None'
City.objects.annotate(population=
Case(
When(
census__date=Max('census__date'),
then='census__value')
), output_field=BigIntegerField()
)
# --> takes forever (not sure what happens at the end, after some minutes I stopped waiting)
Любая помощь очень ценится!
Ответы
Ответ 1
У меня также возникла проблема, когда мне нужен объект максимального значения связанного набора, но мне нужен весь объект. Я не смог найти решение, используя аннотацию и Case. Пока я это сделаю, я использую это предварительное решение. Если в каждом городе нет большого количества объектов переписи или если ваше приложение не связано с производительностью, это может сработать для вас.
inner_qs = Census.objects.order_by('-date')
cities = City.objects.prefetch_related(Prefetch("census_set", queryset=inner_qs, to_attr="census_list"))
class City(models.Model):
@property
def latest_census(self):
if hasattr(self, 'census_list') and len(self.census_list) > 0:
return self.census_list[0]
return None
Если это не сработает для вас, рассмотрите некоторые из предложенных здесь предложений:
http://blog.roseman.org.uk/2010/08/14/getting-related-item-aggregate/
Ответ 2
В настоящий момент они не являются выражением запроса django, чтобы аннотировать не агрегированное поле из связанной модели 1: N на основе выражения sql, содержащего выражение.
Вы можете выполнить это с помощью нескольких обходных решений, таких как разделение запроса и работа с данными в памяти (itertools groupby f.e.) или с помощью необработанных запросов. Но это не будет соответствовать вашим требованиям требований и агностике базы данных.
Я объясню, что я буду делать, если это мое приложение. Для разработчиков трудно получить избыточность в базе данных. В вашем сценарии последний census
на city
является вычисленным полем... но в этом случае принять во внимание материализацию last_census
:
Грязная работа...
class City(models.Model):
last_census = models.ForeignKey('Census', null=True,
blank=True, editable=False)
...
Для удобства обслуживания вы можете переопределить методы save
и delete
на census
, чтобы поддерживать last_census
в актуальном состоянии.
class Census(models.Model):
...
#overriding save
def save(self, *args, **kwargs):
super(Census, self).save(*args, **kwargs)
#updating last_census on referenced city
max_census = Census.objects.filter( city = self.city ).latest('date')
self.city.last_census = max_census.city if max_census else None
self.city.save()
#overriding delete
def delete(self, *args, **kwargs):
#updating last_census on referenced city
max_census = ( Census.objects
.filter( city = self.city )
.exclude( id = self.id )
.latest('date') )
self.city.last_census = max_census.city if max_census else None
self.city.save()
super(Census, self).delete(*args, **kwargs)
Обратите внимание: если вам удобнее, вы можете кодировать его с помощью сигналов (pre_delete, post_save,...) вместо переопределения методов.
Лучшее...
Ваш запрос сейчас:
City.objects.select_related('last_census__value').all()
Ответ 3
Что-то вроде этого может работать для вас:
У меня есть это, чтобы показать последнюю резервацию для ресторанов.
Reservation.objects.filter(
restaurant__city_id__gt=0
).distinct().annotate(city_count=Count(
'restaurant_id')
).order_by('-reservationdate').filter(city_count__gte=1)[:20]
в вашем случае это может быть что-то вроде:
city = Census.objects.filter(
city_id__gt=0
).distinct().annotate(city_count=Count(
'city_id')
).order_by('-date').filter(city_count__gte=1)[:20]
и ваш html
{% for city in city %}
{{ city.city.name }} {{ city.date }}<br>
{% endfor %}
Ответ 4
Поздно, и я проголодался, поэтому я вижу, что это не будет продолжаться до конца *, но следующий код вернет последнее значение переписи в наборе значений для данного города. Возможно, выбор запроса возможен, но опять же я голоден!
* Я не был уверен, нужно ли вам получить все последние значения для всех городов или конкретного города. Последнее довольно легко, первое немного сложнее. Вы можете легко поместить это как метод в свою модель и вызвать его в каждом городе в цикле внутри шаблона/вида.
Надеюсь, это поможет!
from app.models import *
from django.db.models import F
City.objects.annotate(
values=F("census__value"),
date=F("census__date"))\
.values('values', 'name').filter(name="Atlanta")\
.latest('date')