Django ORM按两个相关模型的最大列值过滤

Posted

技术标签:

【中文标题】Django ORM按两个相关模型的最大列值过滤【英文标题】:Django ORM filter by Max column value of two related models 【发布时间】:2018-03-24 18:34:37 【问题描述】:

我有 3 个相关模型:

Program(Model):
    ...  # which aggregates ProgramVersions

ProgramVersion(Model):
    program = ForeignKey(Program)
    index = IntegerField()

UserProgramVersion(Model):
    user = ForeignKey(User)
    version = ForeignKey(ProgramVersion)
    index = IntegerField()

ProgramVersion 和 UserProgramVersion 是基于 index 字段的可订购模型 - 表中具有最高 index 的对象被视为最新/最新对象(这由一些自定义逻辑处理,不相关)。

我想选择所有最新的 UserProgramVersion,即指向同一程序的最新 UPV

这可以由这个 UserProgramVersion 查询集处理:

def latest_user_program_versions(self):
    latest = self\
        .order_by('version__program_id', '-version__index', '-index')\
        .distinct('version__program_id')

    return self.filter(id__in=latest)

这很好用,但是我正在寻找不使用 .distinct() 的解决方案 我试过这样的事情:

def latest_user_program_versions(self):
    latest = self\
        .annotate(
             'max_version_index'=Max('version__index'),
             'max_index'=Max('index'))\
        .filter(
             'version__index'=F('max_version_index'),
             'index'=F('max_index'))

    return self.filter(id__in=latest)

这不起作用

【问题讨论】:

我不相信没有用户过滤器或用户组过滤器“工作正常”。如果你现在省略它,你可能会得到一个以后无论如何都无法添加的答案。 【参考方案1】:

在 Django 1.11 中使用 Subquery() expressions。 docs中的示例类似,目的也是为了获取所需父记录的最新项目。

(您可能可以从该示例开始使用您的对象,但我还写了一个完整的更复杂的建议以避免可能的性能缺陷。)

from django.db.models import OuterRef, Subquery

...
def latest_user_program_versions(self, *args, **kwargs):
    # You should filter users by args or kwargs here, for performance reasons.
    # If you do it here it is applied also to subquery - much faster on a big db.
    qs = self.filter(*args, **kwargs)
    parent = Program.objects.filter(pk__in=qs.values('version__program'))
    newest = (
        qs.filter(version__program=OuterRef('pk'))
        .order_by('-version__index', '-index')
    )
    pks = (
        parent.annotate(newest_id=Subquery(newest.values('pk')[:1]))
        .values_list('newest_id', flat=True)
    )
    # Maybe you prefer to uncomment this to be it compiled by two shorter SQLs.
    # pks = list(pks)
    return self.filter(pk__in=pks)

如果你大大改进了它,请在你的答案中写下解决方案。


编辑 您的问题在您的第二个解决方案中: 没有人可以在他下面剪下一个分支,在 SQL 中也不行,但我可以在子查询中坐在它的临时副本上,以便能够生存:-) 这也是我一开始要求过滤器的原因。第二个问题是 Max('version__index') 和 Max('index') 可能来自两个不同的对象,没有找到有效的交集。


EDIT2已验证:我查询的内部 SQL 很复杂,但似乎是正确的。

SELECT app_userprogramversion.id,...
FROM app_userprogramversion
WHERE app_userprogramversion.id IN
   (SELECT
       (SELECT U0.id
        FROM app_userprogramversion U0
        INNER JOIN app_programversion U2 ON (U0.version_id = U2.id)
        WHERE (U0.user_id = 123 AND U2.program_id = (V0.id))
        ORDER BY U2.index DESC, U0.index DESC LIMIT 1
        ) AS newest_id
    FROM app_program V0 WHERE V0.id IN
       (SELECT U2.program_id AS Col1
        FROM app_userprogramversion U0
        INNER JOIN app_programversion U2 ON (U0.version_id = U2.id)
        WHERE U0.user_id = 123
        )
    )

【讨论】:

以上是关于Django ORM按两个相关模型的最大列值过滤的主要内容,如果未能解决你的问题,请参考以下文章

Django ORM:按相关表过滤器的聚合排序

Django ORM使用子查询按关键字搜索文本

Django ORM - 过滤相关对象

Django - 按最大(日期)年份过滤查询集

django orm中的INNER JOIN

Django ORM:按对象列表过滤