子查询 django 查询以从对象中获取最大的不同值并返回这些对象

Posted

技术标签:

【中文标题】子查询 django 查询以从对象中获取最大的不同值并返回这些对象【英文标题】:Subquery django query to get biggest disctint values from objects and return those objects 【发布时间】:2020-02-05 10:08:49 【问题描述】:

所以我有这个查询,我需要重新过滤以从不同的仪表 id 中获取最大的 take_at 字段的值,但我无法让 django orm's/sql 部分进行此查询。

<QuerySet [<Reading: [127] meter installation: 29, type: 1, taken: 2019-10-07 16:06:48.101453+00:00 value: 78.0000, comment: , VAT: 22.00>, <Reading: [126] meter installation: 41, type: 2, taken: 2019-10-07 14:05:32.415905+00:00 value: 7.0000, comment: asdfe, VAT: None>, <Reading: [125] meter installation: 41, type: 2, taken: 2019-10-07 14:02:37.588983+00:00 value: 7.0000, comment: asdfe, VAT: None>, <Reading: [124] meter installation: 49, type: 2, taken: 2019-10-07 12:19:49.067398+00:00 value: 8.0000, comment: , VAT: 2.00>

这个查询包含很多 Reading 对象,但我只需要从不同的仪表安装中获取最大的 take_at 值,我尝试制作注释然后 distinct ,但它们没有一起实现,我对 SQL 有点陌生所以任何帮助都会很棒!

阅读.py

class Reading(DateTrackedModel):
    meter_installation = models.ForeignKey(
        "MeterInstallation",
        on_delete=models.PROTECT,
        related_name="readings",
        null=False,
        blank=False,
        verbose_name=_("Meter Installation"),
    )
    value = models.DecimalField(
        decimal_places=4, max_digits=10, null=False, blank=False, default=0, verbose_name=_("Value")
    )
    price = models.DecimalField(
        decimal_places=4, max_digits=10, null=False, blank=False, default=0, verbose_name=_("Price")
    )
    reading_type = models.ForeignKey(
        "MeterType",
        on_delete=models.PROTECT,
        null=False,
        blank=False,
        related_name="readings",
        verbose_name=_("Reading type"),
    )
    comment = models.TextField(null=False, blank=True, verbose_name=_("Comment"))
    taken_at = models.DateTimeField(null=False, default=now, blank=False, verbose_name=_("Taken at"))
    VAT = models.DecimalField(decimal_places=2, max_digits=10, null=True, blank=True, verbose_name=_("VAT"))
    unit_name = models.CharField(max_length=100, null=False, blank=True, unique=False, verbose_name=_("Unit name"))
    unit_price = models.DecimalField(
        decimal_places=4, max_digits=10, null=False, blank=False, default=0.0, verbose_name=_("Unit price")
    )

仪表安装型号:

class MeterInstallation(ActiveAfterUntilModel, DateTrackedModel, MPTTModel, NamedModel):  # type: ignore
    meter_type = models.ForeignKey(
        MeterType,
        on_delete=models.PROTECT,
        null=False,
        blank=False,
        related_name="installations",
        verbose_name=_("Meter Installation type"),
    )
    parent = TreeForeignKey(
        "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children", db_index=True
    )
    meter = models.ForeignKey(
        Meter, on_delete=models.PROTECT, related_name="installations", null=False, blank=False, verbose_name=_("Meter")
    )
    building = models.ForeignKey(
        Building,
        on_delete=models.PROTECT,
        related_name="meter_installations",
        null=True,
        blank=False,
        verbose_name=_("Building"),
    )
    places = models.ManyToManyField(Place, related_name="meter_installations", blank=False, verbose_name=_("Places"))
    initial_reading = models.DecimalField(
        decimal_places=4, max_digits=10, null=False, blank=False, default=0, verbose_name=_("Initial reading")
    )
    final_reading = models.DecimalField(
        decimal_places=4, max_digits=10, null=True, blank=True, verbose_name=_("Final reading")
    )

【问题讨论】:

【参考方案1】:

目前尚不清楚您的Reading QuerySet 的当前状态是什么,但您可以在文档中找到here 的一般执行方式。在你的情况下,它应该是这样的:

reading_qs.values('meter_installation').annotate(max_taken_at=models.Max('taken_at'))

更新:

所以在第一篇文章中并不是很清楚,但是您遇到了greatest-n-per-group 问题。 (在你的情况下 n=1)

解决此特定版本问题的一种方法是通过window query(如果您的数据库支持)。应该是这样的:

    reading_qs.annotate(max_taken_at=Window(
        expression=Max('taken_at'),
        partition_by=F('meter_installation')
    )).filter(max_taken_at=F('taken_at'))

更新:这实际上是行不通的,因为Window 注释不可过滤。我认为为了过滤窗口注释,您需要将其包装在Subquery 中,但是使用Subquery,您实际上没有义务使用Window 函数,还有另一种方法可以做到这一点,这是我的下一个例子。

另一种方法是通过subquery 它看起来像:

reading_qs.annotate(
    max_taken_at=Subquery(
            reading_qs.filter(meter_installation=OuterRef('meter_installation'))
            .values('meter_installation')
            .annotate(max_taken_at=Max('taken_at'))
            .values('max_taken_at')
    )
).filter(max_taken_at=F('taken_at'))

第三种解决方案,即 PostgreSQL 唯一的解决方案是:

reading_qs.order_by(
    'meter_installation', '-taken_at'
).distinct('meter_installation')

【讨论】:

对不起,我可能没有在标题中说清楚,我需要对象本身而不仅仅是值。这将返回所需的值,但它只返回来自查询集的对象的values,然后应用程序中的所有内容都会中断,因为我需要读取对象本身而不仅仅是来自查询集的值 所以您希望Reading 对象的taken_at 值等于同一meter_installation 组中所有Reading 对象中最大的taken_at 值,对吗? (问题不是很清楚) 是的,我只想从一个meter_installation 组中获取一个具有最大taken_at 值的Reading 对象,正如您所说的那样 非常感谢您对这些查询的帮助,我还需要深入研究您编写的这些查询并更好地了解这里发生了什么 是的,这是一个很大的话题,也许在普通 SQL 中对这个问题的一个很好的介绍是this one,看看它。您说您是 SQL 新手,您应该首先真正了解 SQL,以便了解 ORM 是如何/做什么/为什么做的。

以上是关于子查询 django 查询以从对象中获取最大的不同值并返回这些对象的主要内容,如果未能解决你的问题,请参考以下文章

优化查询以从不同的表中获取唯一(用户)记录

Spring Mongo 聚合查询以从 MongoDB 获取不同的国家名称和国家代码

Django Q过滤器,无法在单个查询中获得结果

如何编写 Django 查询以获取链接到另一个对象的不同对象?

如何在 SQL 查询中获取最大 Sr 的记录

子查询 MYSQL 以从服务器中选择所有数据库