python 包含数组字段和系数的复杂Django查询

aor9mmx1  于 2023-01-19  发布在  Python
关注(0)|答案(1)|浏览(113)

一方面,让我们考虑这个Django模型:

from django.db import models
from uuid import UUID

class Entry(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    value = models.DecimalField(decimal_places=12, max_digits=22)
    items = ArrayField(base_field=models.UUIDField(null=False, blank=False), default=list)

另一方面,假设我们有这样一本字典:

coefficients = {item1_uuid: item1_coef, item2_uuid: item2_coef, ... }

Entry.value旨在根据coefficientsEntry.items之间分配。

    • 使用Django ORM,给定系数,最有效的方法是什么(在一个SQL查询中)来获得我的Entries对于一个Item的值的总和?**

例如,对于下面的item1,我想得到168.5454...,也就是说100 * 1 + 150 * (0.2 / (0.2 + 0.35)) + 70 * 0.2
| 条目ID|价值|项目|
| - ------|- ------|- ------|
| uuid1|一百|[item1_uuid]|
| uuid2|一百五十|[item1_uuid, item2_uuid]|
| uuid3|七十|[item1_uuid, item2_uuid, item3_uuid]|

coefficients = { item1_uuid: Decimal("0.2"), item2_uuid: Decimal("0.35"), item3_uuid: Decimal("0.45") }

附加问题:我如何调整我的模型以使这个查询运行得更快?我特意选择使用ArrayField,而决定不使用ManyToManyField,这是个坏主意吗?如何知道在哪里可以为这个特定的查询添加db_index[es]
我使用的是Python 3.10、Django 4.1和Postgres 14。

ulydmbyx

ulydmbyx1#

我已经找到了一个解决我自己的问题的方法,但我相信这里的人可以想出一个更有效和更清洁的方法。
这里的想法是将.alias()方法(参见Django文档)和conditional expressionsCaseWhen链接在一个for循环中。
这会导致查询过于复杂,但至少可以按预期工作:

def get_value_for_item(coefficients, item):
        item_coef = coefficients.get(item.pk, Decimal(0))
        if not item_coef:
                return Decimal(0)
        several = Q(items__len__gt=1)
        queryset = (
                Entry.objects
                .filter(items__contains=[item.pk])
                .alias(total=Case(When(several, then=Value(Decimal(0)))))
        )
        for k, v in coefficients.items():
                has_k = Q(items__contains=[k])
                queryset = queryset.alias(total=Case(
                        When(several & has_k, then=Value(v) + F("total")),
                        default="total",
                )
        )
        return (
                queryset.annotate(
                        coef_applied=Case(
                                When(several, then=Value(item_coef) / F("total") * F("value")),
                                default="value",
                        )
                ).aggregate(Sum("coef_applied", default=Decimal(0)))
        )["coef_applied__sum"]

对于我在问题中给出的示例,对于item1,此函数的输出是Decimal(168.5454...),正如预期的那样。

相关问题