【问题标题】:Django ORM subquery with window function带有窗口函数的 Django ORM 子查询
【发布时间】:2018-11-14 07:03:32
【问题描述】:

我正在尝试使用 Django 的 ORM 进行此查询:

SELECT
  id,
  pn,
  revision,
  description
FROM (SELECT
        id,
        pn,
        revision,
        MAX(revision)
        OVER (
          PARTITION BY pn ) max_rev,
        description
      FROM table) maxarts
WHERE revision = max_rev

结果需要是一个查询集,我已经尝试了所有我知道的 Window/OuterRef/Subquery 组合,但没有成功。 我必须使用原始查询吗?

提前致谢 马可

编辑#1:

我会尝试更好地解释,我有一个看起来像这样的模型:

class Article(models.Model):
  pn = models.CharField()
  revision = models.CharField()
  description = models.CharField()

  class Meta:
    unique_together = [("pn", "revision"), ]

数据是这样的:

pn1    rev1    description
pn1    rev2    description
pn2    rev1    anotherdescription
pn1    rev3    description
pn2    rev2    anotherdescription

我需要一个只包含 Max("revision") 值的查询集,每次用户对对象进行修改时,该值都会递增。 我希望现在更清楚了。谢谢!

编辑#2

按照建议,我正在写我已经尝试过的内容:

使用第一条消息中编写的查询的原始 SQL,仅选择 id 字段并将其作为 id__in=ids 传递给 ORM。慢得要命,无法使用。

声明了一个用作过滤器的窗口函数:

Article.objects.annotate(
    max_rev=Window(expression=Max("revision"), partition_by=F("pn"))
).filter(revision=F("max_rev"))

但是 Django 抱怨我不能在 where 子句中使用窗口函数(这是正确的)。

然后我尝试将窗口用作子查询:

window_query = Article.objects.annotate(
    max_rev=Window(expression=Max("revision"), partition_by=F("pn"))
)
result = Article.objects.filter(revision=Subquery(window_query))

我也尝试过使用 OuterRef,将 max_rev 注释用作连接,但没有运气。 我没有主意了!

【问题讨论】:

  • 也许最好解释一下你到底想要什么。我知道这可能是 XY 问题。

标签: django orm window subquery max


【解决方案1】:

我认为你可以通过使用 FirstValue 而不是 Max,得到你所追求的,与你所拥有的没有太大区别:

>>> window_query = Article.objects.annotate(max_id=Window(
        expression=FirstValue("id"),
        partition_by=F("pn"),
        order_by=F("revision").desc()
    )).values("max_id")
>>> list(Article.objects.filter(id__in=Subquery(window_query)))
[<Article: Article object (4)>, <Article: Article object (5)>]

这会产生如下 SQL:SELECT * FROM articles_article WHERE id IN (SELECT FIRST_VALUE(id) OVER (PARTITION BY pn ORDER BY revision DESC) AS max_id FROM articles_article)

子查询表示按修订降序排列窗口,按 pn 分区,并从每个分区中获取第一个 ID;然后我们在父查询中使用它来获取这些 ID 的相关文章。

在 PostgreSQL 上,你也可以这样做:

>>> Article.objects.order_by('pn', '-revision').distinct('pn')
<QuerySet [<Article: Article object (4)>, <Article: Article object (5)>]>

这会产生类似SELECT DISTINCT ON (pn) * FROM articles_article ORDER BY pn ASC, revision DESC 的 SQL。

【讨论】:

    【解决方案2】:

    所以每次对文章进行修订时,都会在表格中创建一行?

    如果是这样,您需要做的就是执行一个计数查询,对所有行进行计数并根据“pn”字段对它们进行分组。如果您想使用Max 函数,那么我建议将“pn”字段替换为IntegerFieldDecimalField,而不是使用CharField。尽管取决于您的应用程序所在的位置,但这可能非常困难。

    from django.db.models import Count
    
    Article.objects.values('pn').annotate(maxvalues=Count('pn'))
    

    【讨论】:

    • 是的,pn(部件号)字段将保持不变,但修订版将更改并写入新行。不幸的是,我无法更改数据类型,因为它是从外部系统导入的,而且我需要一个查询集,因此“值”不是一个选项。无论如何感谢您的帮助!
    猜你喜欢
    • 2019-01-02
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    • 2020-05-26
    • 2014-05-19
    • 2022-01-21
    • 1970-01-01
    • 2023-02-05
    相关资源
    最近更新 更多