Django ORM 按两个相关模型的 Max 列值过滤
Django ORM filter by Max column value of two related models
我有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
字段的可订购模型 - table 中具有最高 index
的对象被视为 latest/newest 对象(这由一些自定义处理逻辑,不相关)。
我想要 select 所有最新的 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)
但是这不起作用
在 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
)
)
我有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
字段的可订购模型 - table 中具有最高 index
的对象被视为 latest/newest 对象(这由一些自定义处理逻辑,不相关)。
我想要 select 所有最新的 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)
但是这不起作用
在 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
)
)