python - Django 子查询表达式包含混合类型。您必须设置output_field

标签 python django subquery django-annotate

我正在尝试添加我的 models.py 中应纳税的学分总和。如果我在没有taxable_credits的情况下计算余额,它就可以工作。当我将taxable_credits添加到混合中时,我收到错误。

class Account(models.Model):
    name = models.CharField(max_length=32)


class Debit(models.Model):
    account = models.ForeignKey(Account, related_name='debits', on_delete=models.CASCADE)
    amount = models.DecimalField(max_digits=12, decimal_places=2)


class Credit(models.Model):
    account = models.ForeignKey(Account, related_name='credits', on_delete=models.CASCADE)
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    taxable = models.BooleanField(default=False)

我的测试如下:

lass TestAccount(TestCase):
    # https://mixedquantum.blogspot.com/2017/08/django-tips-3-subquery-expressions.html

    def setUp(self) -> None:
        self.accounts = dict()
        self.accounts['fox'] = Account.objects.create(name='FOX')
        self.accounts['dog'] = Account.objects.create(name='DOG')
        self.accounts['snake'] = Account.objects.create(name='SNAKE')
        """
        # Credits
        +----------------+-----------------+-----------------+
        | account_name   |   credit_amount | taxable         |
        |----------------+-----------------|-----------------+
        | FOX            |           100.0 | False           |
        | SNAKE          |            50.0 | False           |
        | SNAKE          |            20.0 | False           |
        | DOG            |           300.0 | False           |
        | DOG            |           100.0 | True            |
        +----------------+-----------------+-----------------+
        """
        Credit.objects.create(account=self.accounts['fox'], amount=Decimal('100.0'))
        Credit.objects.create(account=self.accounts['snake'], amount=Decimal('50.0'))
        Credit.objects.create(account=self.accounts['snake'], amount=Decimal('20.0'))
        Credit.objects.create(account=self.accounts['dog'], amount=Decimal('300.0'))
        Credit.objects.create(account=self.accounts['dog'], amount=Decimal('100.0'), taxable=True)
        """
        # Debits
        +----------------+----------------+
        | account_name   |   dedit_amount |
        |----------------+----------------|
        | FOX            |           40.0 |
        | SNAKE          |           30.0 |
        | DOG            |           12.0 |
        | DOG            |           23.0 |
        +----------------+----------------+
        """
        Debit.objects.create(account=self.accounts['fox'], amount=Decimal('40.0'))
        Debit.objects.create(account=self.accounts['snake'], amount=Decimal('30.0'))
        Debit.objects.create(account=self.accounts['dog'], amount=Decimal('12.0'))
        Debit.objects.create(account=self.accounts['dog'], amount=Decimal('23.0'))

    def test_sum(self):
        credits = Credit.objects.filter(
            account=OuterRef('pk')).values('account_id').annotate(sum_credits=Sum('amount'))
        taxable_credits = Credit.objects.filter(
            account=OuterRef('pk'), taxable=True).values('account_id').annotate(sum_taxable_credits=Sum('amount'))
        debits = Debit.objects.filter(
            account=OuterRef('pk')).values('account_id').annotate(sum_debits=Sum('amount'))

        balances = Account.objects.annotate(
            credit_sum=Subquery(credits.values('sum_credits')),
            taxable_credit_sum=Subquery(taxable_credits.values('sum_taxable_credits')),
            debit_sum=Subquery(debits.values('sum_debits')),
            balance= F('credit_sum') - F('debit_sum') - F('taxable_credit_sum')
        ).values_list('name', 'balance')  # , 'taxable_credit_sum')

        self.assertEqual(balances[0], ('FOX', Decimal('60.0')))
        self.assertEqual(balances[2], ('SNAKE', Decimal('40.0')))
        self.assertEqual(balances[1], ('DOG', Decimal('365.0')))
        """
        [('FOX', Decimal('60.00')),
         ('SNAKE', Decimal('40.00')),
         ('DOG', Decimal('265.00'))]
        """

运行测试时,我得到以下错误回溯

Error
Traceback (most recent call last):
  File "/opt/project/alpha_clinic/banking/tests/tests_models.py", line 65, in test_sum
    self.assertEqual(balances[0], ('FOX', Decimal('60.0')))
  File "/usr/local/lib/python3.6/site-packages/django/db/models/query.py", line 308, in __getitem__
    qs._fetch_all()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/query.py", line 1242, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/usr/local/lib/python3.6/site-packages/django/db/models/query.py", line 144, in __iter__
    return compiler.results_iter(tuple_expected=True, chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1085, in results_iter
    results = self.execute_sql(MULTI, chunked_fetch=chunked_fetch, chunk_size=chunk_size)
  File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1120, in execute_sql
    sql, params = self.as_sql()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 474, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 54, in pre_sql_setup
    self.setup_query()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 45, in setup_query
    self.select, self.klass_info, self.annotation_col_map = self.get_select()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 254, in get_select
    sql, params = self.compile(col, select_format=True)
  File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 407, in compile
    return node.output_field.select_format(self, sql, params)
  File "/usr/local/lib/python3.6/site-packages/django/utils/functional.py", line 80, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 258, in output_field
    output_field = self._resolve_output_field()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 290, in _resolve_output_field
    sources_iter = (source for source in self.get_source_fields() if source is not None)
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 344, in get_source_fields
    return [e._output_field_or_none for e in self.get_source_expressions()]
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 344, in <listcomp>
    return [e._output_field_or_none for e in self.get_source_expressions()]
  File "/usr/local/lib/python3.6/site-packages/django/utils/functional.py", line 80, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 271, in _output_field_or_none
    return self.output_field
  File "/usr/local/lib/python3.6/site-packages/django/utils/functional.py", line 80, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 258, in output_field
    output_field = self._resolve_output_field()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 1010, in _resolve_output_field
    return super()._resolve_output_field()
  File "/usr/local/lib/python3.6/site-packages/django/db/models/expressions.py", line 293, in _resolve_output_field
    raise FieldError('Expression contains mixed types. You must set output_field.')
django.core.exceptions.FieldError: Expression contains mixed types. You must set output_field.

我尝试将output_field添加到子查询中,这对我来说没有意义,但又出现另一个错误。

指导将不胜感激

最佳答案

您可以在不使用子查询的情况下使用其他聚合函数来创建这些注释。这个答案中使用的所有函数都是这样导入的

from django.db.models import Sum, Case, When, F, Value

要注释相关记录的总和,您可以执行以下操作:

Account.objects.annotate(
    credit_sum=Sum('credits__amount')
).values('name', 'credit_sum')
# <QuerySet [{'name': 'FOX', 'credit_sum': Decimal('100.00')}, {'name': 'DOG', 'credit_sum': Decimal('400.00')}, {'name': 'SNAKE', 'credit_sum': Decimal('70.00')}]>

这样做的一个问题是,当您对 2 个关系求和时,如果一条记录有多个相关行,您将得到重复项(此处“DOG”的所有值都加倍)

Account.objects.annotate(
    credit_sum=Sum('credits__amount'),
    debit_sum=Sum('debits__amount')
).values('name', 'credit_sum', 'debit_sum')
# <QuerySet [{'name': 'FOX', 'credit_sum': Decimal('100.00'), 'debit_sum': Decimal('40.00')}, {'name': 'DOG', 'credit_sum': Decimal('800.00'), 'debit_sum': Decimal('70.00')}, {'name': 'SNAKE', 'credit_sum': Decimal('70.00'), 'debit_sum': Decimal('60.00')}]>

要计算不同值,您可以将 distinct= 添加到 Sum

Account.objects.annotate(
    credit_sum=Sum('credits__amount', distinct=F('credits__id')),
    debit_sum=Sum('debits__amount', distinct=F('debits__id'))
)

要有条件地求和或计算行数,您可以使用 Case and When ,对于您的 taxable_credit_sum 注释,它看起来像这样

balances = Account.objects.annotate(
    credit_sum=Sum('credits__amount', distinct=F('credits__id')),
    debit_sum=Sum('debits__amount', distinct=F('debits__id')),
    taxable_credit_sum=Sum(Case(
        When(credits__taxable=True, then=F('credits__amount')),
        default=Value(0),
        output_field=models.DecimalField()
    ), distinct=F('credits__id'))
).annotate(
    balance=F('credit_sum') - F('debit_sum') - F('taxable_credit_sum')
).order_by('id').values_list('name', 'balance')

现在您应该拥有所需的查询

您不需要同时计算抵免额和非应税抵免额

由于您只是从另一个中减去一个,因此您只需为此创建一个注释,将所有不需纳税的学分相加即可

balances = Account.objects.annotate(
    debit_sum=Sum('debits__amount', distinct=F('debits__id')),
    non_taxable_credit_sum=Sum(Case(
        When(credits__taxable=False, then=F('credits__amount')),
        default=Value(0),
        output_field=models.DecimalField()
    ), distinct=F('credits__id'))
).annotate(
    balance=F('non_taxable_credit_sum') - F('debit_sum')
).order_by('id').values_list('name', 'balance')

关于python - Django 子查询表达式包含混合类型。您必须设置output_field,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59593570/

相关文章:

python - 如何根据屏幕大小更改 django 模板中的列数

Django Cookies,我该如何设置它们?

mysql - 从子查询中检索多列

MYSQL - 当 DO 存在和不存在时进行 SubSelect

python - 名称 'datasets' 未定义

python - groupby 中的 Pandas 加权模式

python - Django TypeError ("' %s' 是此函数的无效关键字参数")

python - 将 range() 的结果存储为列表变量 - Python

python - 我如何在 Django 中从 HTML 标签按钮在后台触发 Python 脚本?

用于图表的 mysql 子查询