【问题标题】:Django QuerySet Two-Valued SubqueryDjango QuerySet 二值子查询
【发布时间】:2018-10-28 15:26:28
【问题描述】:

给定一个模型

class Entity(models.Model):
    identifier = models.IntegerField()
    created = models.IntegerField()
    content = models.IntegerField()

    class Meta:
        unique_together = (('identifier', 'created'))

我想查询所有created 为最大的对象,在具有共同identifier 的对象中。

在 SQL 中,子查询中的窗口函数解决了这个问题:

SELECT identifier, content
  FROM entity
  WHERE (identifier, created)
    IN (SELECT identifier, max(created) OVER (PARTITION BY identifier)
          FROM entity);

另请参阅:http://sqlfiddle.com/#!17/c541f/1/0

窗口函数和子查询在 Django 2.0 中都可用。但是,我还没有找到一种方法来表达具有多列的子查询表达式。

有没有办法将该 SQL 查询转换为 Django QuerySet 世界?这可能是一个 XY 问题,我的问题可以通过不同的方式解决吗?

我丑陋的解决方法是

Entity.objects.raw('''
SELECT * FROM app_entity e
 WHERE e.created = (SELECT max(f.created) FROM app_entity f WHERE e.identifier = f.identifier)''')

因为底层的 sqlite3 版本显然不能处理多列子查询。

【问题讨论】:

    标签: django django-queryset


    【解决方案1】:

    Postgres specific version 工作得非常好,但不能很好地与必须在崩溃之前/之后发生的过滤结合使用。它也不能订购。

    我所做的是在子查询中使用它:

    class LatestQuerySet(models.QuerySet):
        def latest_objects(self):
            # Get the latest version of every object matching the current query
            latest = (
                self
                # Sort by identifier with latest version first
                .order_by("identifier", "-created")
                # This only works on Postgres
                .distinct("identifier")
            )
    
            # Return a new queryset that includes the subquery
            return self.filter(id__in=latest)
    

    然后可以像这样组合:

    # Find the latest version of every object that is at least staged for publication
    # and check whether that object should be published in its latest version.
    Entity.objects\
        .filter(state__gte=STAGED_FOR_PUBLISH)\
        .latest_objects()\
        .filter(include_entity=True)
    

    【讨论】:

    • 你不需要做子查询:你可以直接在 QuerySet 中使用 .distinct('identifier')。
    • 如果你只在DISTINCT之前下单和过滤,你可以直接做DISTINCT(通常需要确保正确的物品出来)。您不能对剩余的结果进行排序和过滤。如果您希望在 DISTINCT 之后进行过滤/排序,则需要子查询。
    • 您仍然可以过滤 after 一个 distinct - 实际上您可以应用排序 after your .distinct: Person.objects.distinct('company__name').order_by('company', 'pk').filter(first_name='X')。但是,您是正确的,因为这实际上与(在某些情况下)作为子查询不同:在我的情况下,任何名为“X”的人都可以工作,而不是预期的“人公司中第一个叫 X 的人。
    • (即,Django 将以任意顺序处理 .filter()、.order_by() 和 .distinct(on) 语句,因此它是一个有效的查询,但它可能意味着 一些不同的东西。我在你的例子中错过了)。
    • 是的,之前和之后,我的意思是应用操作的顺序,而不是查询集中的文字顺序。排序时,您通常可以通过对多个列进行排序来解决此问题(只要它们不冲突),但对于过滤,它可以轻松更改结果。
    【解决方案2】:

    我认为你可以用另一种方式来做(但我不确定它是否会比窗口表达式表现更好或更差)......

    max_created = Entity.objects.filter(
        identifier=OuterRef('identifier')
    ).order_by('-created').values('created')[:1]
    
    Entity.objects.filter(
        created=Subquery(max_created)
    )
    

    这会获取给定 identifier 的最大 created 值,作为相关子查询,然后仅过滤匹配的那些。

    这可能需要调整:我不确定您是否可以像这样过滤子查询,或者您是否需要 .annotate(max_created=Subquery(created)).filter(created=F('max_created')) 或其他类似的可怕的东西。

    此外,如果您使用的是 postgres,则可以使用 DISTINCT ON 功能获得一个非常简洁的解决方案:

    Entity.objects.order_by('identifier', '-created').distinct('identifier')
    

    【讨论】:

      猜你喜欢
      • 2011-04-15
      • 2020-08-06
      • 1970-01-01
      • 2019-08-05
      • 2020-08-14
      • 2016-02-16
      • 1970-01-01
      • 2019-08-03
      • 2018-01-22
      相关资源
      最近更新 更多