Skip to main content
 首页 » 编程设计

python之Django ORM 按两个相关模型的 Max 列值过滤

2024年10月01日35unruledboy

我有 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) 

但是这不起作用

请您参考如下方法:

使用 Subquery() expressions 在 Django 1.11 中。 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 
        ) 
    )