2016-06-16 6 views
10

Как рассчитать общий объем за месяц без использования дополнительных?Django: Группа запросов по месяцам

настоящее время я использую:

  • Джанго 1,8
  • Postgre 9.3.13
  • Python 2,7

Пример.

enter image description here

То, что я пытался до сих пор.

#Doesn't work for me but I don't mind because I don't want to use extra 
truncate_month = connection.ops.date_trunc_sql('month','day') 
invoices = Invoice.objects.filter(is_deleted = False,company = company).extra({'month': truncate_month}).values('month').annotate(Sum('total')) 

---- 
#It works but I think that it's too slow if I query a big set of data 
for current_month in range(1,13): 
    Invoice.objects.filter(date__month = current__month).annotate(total = Sum("total")) 

, а также это один, ответ кажется большим, но я не могу импортировать модуль TruncMonth.

Django: Group by date (day, month, year)


P.S. Я знаю, что этот вопрос уже задан несколько раз, но я не вижу ответа.

Спасибо!


РЕШЕНИЕ:

Благодаря @ ответ Vin-G.

enter image description here

+1

Пожалуйста, покажите, что вы пытались так для –

+0

вы могли видеть этот пример для вдохновения http://stackoverflow.com/a/8746532/2581266 –

+1

@MushahidKhan Пожалуйста, проверьте мой обновленный вопрос. Благодаря! – aldesabido

ответ

12

Во-первых, вы должны сделать функцию, которая может извлечь месяц для вас:

from django.db import models 
from django.db.models import Func 

class Month(Func): 
    function = 'EXTRACT' 
    template = '%(function)s(MONTH from %(expressions)s)' 
    output_field = models.IntegerField() 

После этого все, что вам нужно сделать, это

  1. аннотировать каждая строка с месяца
  2. группировать результаты по аннотированному месяцу с использованием values()
  3. аннотировать каждый результат с суммарной суммой итогов с использованием Sum()

Важно: если ваш класс модели имеет порядок по умолчанию, указанные в мета-параметры, то вам придется добавить пустое order_by() положение. Это происходит из-за https://docs.djangoproject.com/en/1.9/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

полей, которые упоминаются в order_by() части QuerySet (или которые используются при заказе по умолчанию на модели) используются при выборе выходных данных, даже если они не указано иное в вызове values(). Эти дополнительные поля используются для группировки «похожих» результатов вместе, и они могут сделать иначе идентичные строки результатов, как представляется, отдельными.

Если вы не уверены, вы можете просто добавить пустоту order_by() в любом случае без каких-либо неблагоприятных последствий.

т.е.

from django.db.models import Sum 

summary = (Invoice.objects 
       .annotate(m=Month('date')) 
       .values('m') 
       .annotate(total=Sum('total')) 
       .order_by()) 

Смотрите полный суть здесь: https://gist.github.com/alvingonzales/ff9333e39d221981e5fc4cd6cdafdd17

Если вам нужна дополнительная информация:

Сведения о создании собственных классов Func: https://docs.djangoproject.com/en/1.8/ref/models/expressions/#func-expressions

Подробная информация о значениях() (обратите внимание на то, как он взаимодействует с аннотатом() в отношении порядка предложений): https://docs.djangoproject.com/en/1.9/topics/db/aggregation/#values

порядок, в котором аннотировать() и значения() положения применяются к запросу является значительным. Если предложение values ​​() предшествует аннотации(), аннотация будет вычисляться с использованием группировки, описываемой предложением values ​​().

+0

Спасибо за подробный ответ. Я пробовал ваш ответ, но месяцы не группируются. Он просто вычисляет общее количество транзакций. – aldesabido

+0

Чтобы получить результат, нажмите ссылку ниже. https://s32.postimg.org/3ripkuelh/Testdata_result.png – aldesabido

+0

Наконец-то он работает. Я только что добавил .order_by() после последнего аннотата, а затем он работает как волшебство. :) Благодаря. – aldesabido

1

Я не знаю, если мое решение быстрее, чем ваш. Вы должны просмотреть профиль. Тем не менее я только запрашиваю db один раз вместо 12 раз.

#utils.py 
from django.db.models import Count, Sum 


def get_total_per_month_value(): 
    """ 
    Return the total of sales per month 

    ReturnType: [Dict] 
    {'December': 3400, 'February': 224, 'January': 792} 
    """ 
    result= {} 
    db_result = Sale.objects.values('price','created') 
    for i in db_result: 
     month = str(i.get('created').strftime("%B")) 
     if month in result.keys(): 
      result[month] = result[month] + i.get('price') 
     else: 
      result[month] = i.get('price') 
    return result 

#models.py 
class Sale(models.Model): 
    price = models.PositiveSmallIntegerField() 
    created = models.DateTimeField(_(u'Published'), default="2001-02-24") 

#views.py 
from .utils import get_total_per_month_value 
# ... 
result = get_total_per_month_value() 

test.py

# 
    import pytest 
    from mixer.backend.django import mixer 
    #Don't try to write in the database 
    pytestmark = pytest.mark.django_db 
    def test_get_total_per_month(): 
     from .utils import get_total_per_month_value 
     selected_date = ['01','02','03','01','01'] 
     #2016-01-12 == YYYY-MM-DD 
     for i in selected_date: 
      mixer.blend('myapp.Sale', created="2016-"+i+"-12") 
     values = get_total_per_month_value() #return a dict 
     months = values.keys() 
     assert 'January' in months, 'Should include January' 
     assert 'February' in months, 'Should include February' 
     assert len(months) == 3, 'Should aggregate the months' 
4

itertools.groupby является производительным вариантом в Python и может быть использован с одной БД запроса:

from itertools import groupby 

invoices = Invoice.objects.only('date', 'total').order_by('date') 
month_totals = { 
    k: sum(x.total for x in g) 
    for k, g in groupby(invoices, key=lambda i: i.date.month) 
} 
month_totals 
# {1: 100, 3: 100, 4: 500, 7: 500} 

Я не знаю чистого Джанго ОРМ решение. Фильтр date__month очень ограничен и не может использоваться в values, order_by и т. Д.

+0

В Python 3.0 вам может понадобиться использовать i.date(). month. Работал для меня Django 1.10 + python 3. –

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