Django QuerySet二值子查询

时间:2018-05-18 08:32:52

标签: django django-queryset

给定模型

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版本显然无法处理多列子查询。

2 个答案:

答案 0 :(得分: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)

答案 1 :(得分:0)

我认为您可以用另一种方式来做(但是我不确定它的表现是否会好于窗口表达式)

created

这将获取给定identifier的最大.annotate(max_created=Subquery(created)).filter(created=F('max_created'))值,作为相关子查询,然后仅过滤匹配的那些子查询。

这可能需要调整:我不确定是否可以像这样对子查询进行过滤,或者是否需要DISTINCT ON或类似的其他可怕内容。

此外,如果您使用的是Postgres,则可以使用Entity.objects.order_by('identifier', '-created').distinct('identifier') 功能来获得一个真正整洁的解决方案:

#!/usr/bin/expect
set yest [ exec /bin/date -d "yesterday" +%Y%m%d]
send_user $yest
exit 1