Django ORM 聚合与条件 SUM 与组失败

Django ORM aggregation with conditional SUM with group by failing

提问人:Ehsan 提问时间:11/9/2023 最后编辑:Ehsan 更新时间:11/9/2023 访问量:37

问:

我正在尝试在 Django ORM 中执行以下查询,但到目前为止没有运气。

我想将所有 PotatoesMeasurement 分组,并对它何时大于、小于和在某些值范围内进行有条件的计数。我有原始的mongosh查询来获得正确的结果,但我无法在django ORM中获得等效的结果。wnalternaria

我的模型如下所示:

class BasicEntity(models.Model):
    class Meta:
        abstract = True

    id = models.UUIDField(default=uuid4, editable=False, db_index=True, primary_key=True)
    created_on = models.DateTimeField(auto_now_add=True, editable=False, blank=True)
    updated_on = models.DateTimeField(auto_now=True, editable=False, blank=True)

class PotatoesMeasurement(BasicEntity):
    class Meta:
        db_table = "measurements_potatoes"

    colorado_potato_beetle_larvae = models.FloatField( default=0.0,
                               validators=[MinValueValidator(0), MaxValueValidator(100)])
    aphids_per_leaflet = models.IntegerField(blank=False, null=False)
    late_blight = models.FloatField( default=0.0,
                               validators=[MinValueValidator(0), MaxValueValidator(100)])
    alternaria = models.FloatField( default=0.0,
                               validators=[MinValueValidator(0), MaxValueValidator(100)])
    wn = models.IntegerField(default=datetime.now().isocalendar().week,
                             validators=[MinValueValidator(1), MaxValueValidator(53)])

Mongosh查询完美运行:

db.measurements_potatoes.aggregate([
  {
    $group: {
      _id: "$wn",
      countSmaller: { $sum: { $cond: [{ $lte: ["$alternaria", 1] }, 1, 0] } },
      countRange: { $sum:   { $cond: [{ $range: [ 20, "$alternaria", 30 ] }, 1, 0] } },
      countBigger: { $sum:  { $cond: [{ $gt: ["$alternaria", 90] }, 1, 0] } }
    }
  },
  {$sort: {_id: 1}},
]);

到目前为止,在 django ORM 方面,我有以下内容:

res = (
    PotatoesMeasurement.objects.all()
    .values("wn")
    .aggregate(
        countSmaller=Sum(
            Case(When(alternaria__lt=1, then=1), default=0, output_field=IntegerField())
        ),
        countRange=Sum(
            Case(
                When(alternaria__range=[30, 50], then=1),
                default=0,
                output_field=IntegerField(),
            )
        ),
        countBigger=Sum(
            Case(When(alternaria__gt=1, then=1), default=0, output_field=IntegerField())
        ),
    )
    .order_by("wn")
)

上述命令失败,并出现以下错误:

 raise exe from e
djongo.exceptions.SQLDecodeError: 

        Keyword: None
        Sub SQL: None
        FAILED SQL: SELECT SUM(CASE WHEN "measurements_potatoes"."alternaria" < %(0)s THEN %(1)s ELSE %(2)s END) AS "countSmaller", SUM(CASE WHEN "measurements_potatoes"."alternaria" BETWEEN %(3)s AND %(4)s THEN %(5)s ELSE %(6)s END) AS "countRange", SUM(CASE WHEN "measurements_potatoes"."alternaria" > %(7)s THEN %(8)s ELSE %(9)s END) AS "countBigger" FROM "measurements_potatoes"
        Params: (1.0, 1, 0, 30.0, 50.0, 1, 0, 1.0, 1, 0)
        Version: 1.3.6

The above exception was the direct cause of the following exception:

知道查询失败的原因吗?或者也许有其他方法可以执行原始查询?

谢谢

Django mongodb django-models ORM 聚合框架

评论


答:

0赞 KingRanTheMan 11/9/2023 #1

你的 Django ORM 查询将失败,因为 和 一起使用不正确。.aggregate().order_by()

.aggregate()是一个 QuerySet 方法,用于计算整个 QuerySet 的聚合。重要的是,它本身不返回 QuerySet,而是返回一个 Dictionary(参见文档)。

由于它也是一个 QuerySet 方法,而不是一个 Dictionary 方法,因此你不能在 之后链接它。.order_by().aggregate()

相反,如果要按 wn 分组并保留 QuerySet 来排序,则应使用 if you want to group。.annotate()

因此,更正的方法涉及使用来计算每组的条件和,然后根据需要对结果使用。.annotate().order_by()

下面介绍如何相应地重构查询:

from django.db.models import Sum, Case, When, IntegerField

res = (
    PotatoesMeasurement.objects
    .values('wn')
    .annotate(
        countSmaller=Sum(
            Case(
                When(alternaria__lte=1, then=1),
                default=0,
                output_field=IntegerField()
            )
        ),
        countRange=Sum(
            Case(
                When(alternaria__gte=20, alternaria__lt=30, then=1),
                default=0,
                output_field=IntegerField()
            )
        ),
        countBigger=Sum(
            Case(
                When(alternaria__gt=90, then=1),
                default=0,
                output_field=IntegerField()
            )
        )
    )
    .order_by('wn')
)

请注意,我已将条件更改为使用范围的 __gte 和 __lt。countRange

现在,对于抛出的SQLDecodeError,如果上述方法不能解决问题(希望它能解决问题),那么Djongo将这些查询转换为MongoDB的聚合框架的能力可能会受到限制(Djongo是您似乎正在使用的MongoDB的Django ORM包装器)。

恐怕我不是MongoDB专家,所以如果错误继续出现,我无法提供帮助。但希望对你的查询进行上述更改意味着你将向 Djongo 发送一个纯 QuerySet,并且希望它应该可以转换为 MongoDB 查询,因此你的错误应该得到解决。

评论

0赞 Ehsan 11/9/2023
感谢您对文档用法的澄清,也感谢您对文档的了解。您重构的查询运行正常,但是当尝试检查该值时,它会抛出相同的错误,我在这里发现了几个主题,讨论 Django 中 Mongo 聚合框架支持的局限性,所以我将尝试切换到另一个数据库并测试相同的查询。我会在这里发布我的更新order_byaggregate
0赞 KingRanTheMan 11/9/2023
谢谢,@Ehsan - 可悲的是,我对此并不感到太惊讶;Django 的 ORM 确实没有考虑到 NoSQL/MongoDB(同样,MongoDB 也没有考虑到 Django )。这两者一起工作真的很痛苦,虽然在大多数情况下是可能的,但如果你在这件事上有选择,我总是建议你不要尝试将这两种技术结合起来(我对那些别无选择的人无限尊重,所以顽强地坚持下去!
1赞 Ehsan 11/10/2023
只是为了确认限制是由于 ORM 对 MongoDB 的支持,我将数据库更改为 SQLite3,并且上述查询运行良好。