2013-06-20 3 views
8

У меня есть базовая модель, как:QuerySet с .latest() за каждый день

class Stats(models.Model): 

    created = models.DateTimeField(auto_now_add=True) 
    growth = models.IntegerField() 

Я бег сельдерей работы каждый 10 минут, чтобы создать новый объект статистики.

Использование .latest() на QuerySet дает мне последний объект статистики на сегодняшний день.

Тем не менее, я хочу список с одним объектом статистики за каждый день.

Рассмотрим следующий пример:

Stats(growth=100) #created 1/1/13 23:50 
Stats(growth=200) #created 1/1/13 23:59 
Stats(growth=111) #created 1/2/13 23:50 
Stats(growth=222) #created 1/2/13 23:59 

QuerySet должен вернуться позднее в течение каждого дня. В примере один с ростом 200 и 222.

В SQL я запустил подзапрос с максимальным значением для каждого дня и объединил его.

Поскольку я не хочу использовать необработанный SQL, есть ли способ сделать это с помощью ORM django?

+1

Просто, чтобы понять это в голове; если вы хотите получить последнюю за каждый день - в вашем примере вы бы не хотели роста 200 и 222? – Ewan

+0

Да, это так. Я исправил его;) – Jannis

ответ

4

К сожалению, нет никакой возможности (что я знаю .. я выглядел довольно трудно), чтобы избежать использования некоторых вид сырой sql, чтобы выполнить то, что вы хотите сделать (с вашей текущей моделью, см. конец для другого предложения). Но вы можете свести к минимуму воздействие, написав как можно меньше сырья sql. На практике сайты django не должны быть переносимыми в разных базах данных. Если вы не планируете использовать это приложение в другом месте или публично публиковать его, вы должны быть в порядке.

Ниже приведен пример для sqlite. Вы можете использовать , чтобы сопоставить типы баз данных с функциями date, посмотреть тип драйвера и заменить его на правильную, если вам нужно.

>>> for stat in Stats.objects.all(): 
...  print stat.created, stat.growth 
... 
2013-06-22 13:41:25.334262+00:00 3 
2013-06-22 13:41:40.473373+00:00 3 
2013-06-22 13:41:44.921247+00:00 4 
2013-06-22 13:41:47.533102+00:00 5 
2013-06-23 13:41:58.458250+00:00 6 
2013-06-23 13:42:01.282702+00:00 3 
2013-06-23 13:42:03.633236+00:00 1 

>>> last_stat_per_day = Stats.objects.extra( 
      select={'the_date': 'date(created)' } 
     ).values_list('the_date').annotate(max_date=Max('created')) 

>>> last_stat_per_day 
[(u'2013-06-22', datetime.datetime(2013, 6, 22, 13, 41, 47, 533102, tzinfo=<UTC>)), (u'2013-06-23', datetime.datetime(2013, 6, 23, 13, 42, 3, 633236, tzinfo=<UTC>))] 

>>> max_dates = [item[1] for item in last_stat_per_day] 
>>> max_dates 
[datetime.datetime(2013, 6, 22, 13, 41, 47, 533102, tzinfo=<UTC>), 
datetime.datetime(2013, 6, 23, 13, 42, 3, 633236, tzinfo=<UTC>)] 

>>> stats = Stats.objects.filter(created__in=max_dates) 
>>> for stat in stats: 
...  print stat.created, stat.growth 
... 
2013-06-22 13:41:47.533102+00:00 5 
2013-06-23 13:42:03.633236+00:00 1 

я написал здесь раньше, что это только один запрос, но я соврала - values_list нужно преобразовать только вернуть max_date для последующего запроса, а это значит, выполнив инструкцию. Это всего лишь 2 вопроса, которые были бы значительно лучше, чем функция N + 1.

непереносимая бит это:

last_stat_per_day = Stats.objects.extra( 
    select={'the_date': 'date(created)' } 
).values_list('the_date').annotate(max_date=Max('created')) 

extra Использования не является идеальным, но сырой SQL здесь проста, и поддается хорошо зависимому замены драйвера базы данных. Необходимо заменить только date(created). Вы можете обернуть это методом в пользовательском менеджере, если хотите, и затем вы успешно абстрагировали этот беспорядок в одном месте.

Другой вариант - просто добавить DateField к вашей модели, а затем вам не нужно использовать дополнительные файлы вообще. Вы просто замените вызов values_listvalues_list('created_date'), полностью удалите extra и назовите его днем. Стоимость очевидна - требуется больше места для хранения.Также неинтуитивно понятно, почему у вас есть Date и поле DateTime на той же модели. Сохранение двух в синхронизации может также создавать проблемы.

0

Может быть, вы можете сделать somehting как:

import datetime 
day = datetime.datetime.now().day 
the_last_one = Stats.objects.filter(created__day=day).order_by('-created')[0] 

или что-то вроде

the_last_one = Stats.objects.filter(created__day=day).order_by('created').latest() 
+0

они вернут последний объект Stats, а не список объектов с последним stat для каждого дня. – Jannis

0

В дополнение к двум другим ответам, возможно, также рассмотрите возможность хранения результатов в другой модели (особенно если данные в день не сильно меняются после ввода, и у вас есть большие объемы данных). Что-то вроде:

class DailyStat(models.Model): 
    date = models.DateField(unique=True) 
    # Denormalisation yo 
    # Could also store foreign keys to Stats instances if needed 
    max_growth = models.IntegerField() 
    min_growth = models.IntegerField() 
    # . 
    # . 
    # . 
    # and any other stats per day e.g. average per day 

И добавить периодическую сельдерей задачу:

from celery.task.schedules import crontab 
from celery.task import periodic_task 
import datetime 

# Periodic task for 1am daily 
@periodic_task(run_every=crontab(minute=0, hour=1)) 
def process_stats_ery_day(): 
    # Code to populate DailyStat 
    today = datetime.date.today() 
    # Assumes relevant custom Manager methods exist 
    # Can use regular Django ORM methods to achieve this 
    max = Stats.objects.get_max_growth(date=today) 
    min = Stats.objects.get_min_growth(date=today) 
    ds = DailyStat(date=today, max_growth=max.growth, min_growth=min.growth) 
    ds.save() 

получить результаты с:

DailyStat.objects.all() 

Конечно, среди других факторов, чтобы рассмотреть этот подход представляет проблему необходимо обновить DailyStat, когда изменяется прошлый стат и т. д. (signals может использоваться, если вы делаете этот путь.)

0

TruncDate ist new в Django> 2.0 и теперь можно сделать тот же запрос короче, но только в базах данных с поддержкой distinct, например PostgreSQL.

Stats.objects.all().annotate(date=TruncDay('created')).distinct('created').order_by('-date')

Смежные вопросы