Набор запросов Django: Рассчитать среднемесячное значение - PullRequest
0 голосов
/ 12 февраля 2019

У меня есть модель продаж, и я хочу рассчитать (Количество транзакций) / (количество дней), когда сгруппированы по month, week, year.

class SaleItem(models.Model):
    id = models.UUIDField(default=uuid4, primary_key=True)
    bill = models.ForeignKey()
    item = models.ForeignKey('item')
    quantity = models.PositiveSmallIntegerField()
    price = models.DecimalField(max_digits=13, decimal_places=3, default=0)

Такесли продажи сгруппированы по месяцам, это становится (# транзакций / # дней в этом месяце) для каждого месяца.Теперь, если продажи сгруппированы по годам, это становится (# транзакций / # дней в этом году)

В настоящее время я могу получить количество транзакций

aggregate = 'month' # parameter
# get number of transactions
SaleItem.objects.annotate(date=Trunc('bill__date', aggregate)).values('date').annotate(sales=Count('bill', distinct=True))

Но как я могу разделить каждый счетпо количеству дней в этой группе?

1 Ответ

0 голосов
/ 14 февраля 2019

Выполнение этого в SQL возможно (и даже не так сложно).Однако получение количества дней в месяце зависит от СУБД, и нет общей функции базы данных Django, которая защитила бы вас от различных реализаций SQL.

Django позволяет легко обернуть ваши собственные функции в функции SQL.Например, для SQLite вы можете определить

class DaysInMonth(Func):
    output_field = IntegerField()
    def as_sqlite(self, compiler, connection):
        return super().as_sql(
            compiler, 
            connection,
            function='strftime',
            template='''
            %(function)s("%%%%d", 
            %(expressions)s, 
            "start of month", 
            "+1 month", 
            "-1 day")
            ''',
        )

Затем вы можете использовать DaysInMonth(), чтобы разделить ваш счетчик на количество дней:

qs = (
    SaleItem.objects
        .annotate(date=Trunc('bill__date', aggregate))
        .values('date')
        .annotate(
            sales = Count('bill', distinct=True),
            sales_per_day = F('sales') / DaysInMonth('date')
        )
)

Если округлено до целого числане достаточно, и вам нужен десятичный результат, это еще один обруч для прыжка:

sales_per_day=ExpressionWrapper(
    Cast('sales', FloatField()) / DaysInMonth(F('date')), 
    DecimalField()
)

Если, боже упаси, вы хотите округлить в базе данных, а не в своем шаблоне, вам нужна другая пользовательская функция:

class Round(Func):
  function = 'ROUND'
  output_field = FloatField()
  arity = 2

sales_per_day=Round(
    Cast('sales', FloatField()) / DaysInMonth(F('date')), 
    2 # decimal precision
)

Таким образом, Django действительно гибок, но, как сказал Виллем, выполнение этого в Python избавит вас от некоторой боли, не теряя при этом существенной производительности (если она вообще есть).

...