Аннотировать Queryset с логарифмом значения поля в Django - PullRequest
0 голосов
/ 09 ноября 2018

Я исследовал доступные функции агрегирования django и заметил, что там отсутствует логарифм. Итак, мой вопрос: как я могу использовать логарифм в аннотировании Queryset? Я не могу взять логарифм после оценки Queryset, потому что мне нужно аннотировать не точно с логарифмом, а с помощью выражения, содержащего его, например, для некоторых моделей User и Task Мне нужно аннотировать User с помощью F('task__cost') / Log('task__solved_count').

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

1 Ответ

0 голосов
/ 09 ноября 2018

У Django есть эти функции, они были добавлены в запрос на извлечение 9622 [GitHub] . В ветке разработки они уже существуют в модуле django.db.models.functions.math. Но не в версии . В документации по Django есть страница, в которой указан исходный код .

Оказывается, эта функция на самых популярных системах баз данных одинакова [Django ticket] . Вы можете добавить исходный код [GitHub] :

from django.db.models import (
    DecimalField, FloatField, Func, IntegerField, Transform,
)
from django.db.models.functions import Cast

# ...

class DecimalInputMixin:

    def as_postgresql(self, compiler, connection, **extra_context):
        # Cast FloatField to DecimalField as PostgreSQL doesn't support the
        # following function signatures:
        # - LOG(double, double)
        # - MOD(double, double)
        output_field = DecimalField(decimal_places=sys.float_info.dig, max_digits=1000)
        clone = self.copy()
        clone.set_source_expressions([
            Cast(expression, output_field) if isinstance(expression.output_field, FloatField)
            else expression for expression in self.get_source_expressions()
        ])
        return clone.as_sql(compiler, connection, **extra_context)

class OutputFieldMixin:

    def _resolve_output_field(self):
        has_decimals = any(isinstance(s.output_field, DecimalField) for s in self.get_source_expressions())
        return DecimalField() if has_decimals else FloatField()

# ...

class Log(DecimalInputMixin, OutputFieldMixin, Func):
    function = 'LOG'
    arity = 2

    def as_sqlite(self, compiler, connection, **extra_context):
        if not getattr(connection.ops, 'spatialite', False):
            return self.as_sql(compiler, connection)
        # This function is usually Log(b, x) returning the logarithm of x to
        # the base b, but on SpatiaLite it's Log(x, b).
        clone = self.copy()
        clone.set_source_expressions(self.get_source_expressions()[::-1])
        return clone.as_sql(compiler, connection, **extra_context)

, а затем импортируйте определенную функцию Log и используйте ее следующим образом:

User.objects.annotate(cost_solve_ratio=F('task__cost') / Log('task__solved_count'))
...