Django ORM 查询限制特定键实例

1 投票
1 回答
1391 浏览
提问于 2025-04-15 11:56

Projectfundingdetail 表里有一个外键指向 project 表。

下面这个查询可以让我得到所有项目中,任何一个 projectfundingdetail 低于 1000 的项目列表。现在我想限制结果,只显示最新的 projectfundingdetail。

projects_list.filter(projectfundingdetail__budget__lte=1000).distinct()

我定义了以下这个函数,

def latest_funding(self):
    return self.projectfundingdetail_set.latest(field_name='end_date')

但是我不能使用下面的,因为 latest_funding 不是数据库里的字段。

projects_list.filter(latest_funding__budget__lte=1000).distinct()

那么我应该用什么查询来获取所有项目,这些项目只有它们最新的 projectfundingdetail 低于 1000 呢?

1 个回答

3

这个查询看起来比想象中要复杂。根据我所知道的,Django的ORM(对象关系映射)并没有提供生成高效SQL的方式,因为高效的SQL需要一个相关的子查询。(如果我错了,请纠正我!)你可以用这个查询生成一些不太好看的SQL:

Projectfundingdetail.objects.annotate(latest=Max('project__projectfundingdetail__end_date')).filter(end_date=F('latest')).filter(budget__lte==1000).select_related()

但是这需要从Projectfundingdetail表连接到Project表再返回,这样效率不高(不过可能对你的需求来说已经足够了)。

另一种方法是写原生SQL,并把它封装在一个管理方法里。虽然看起来有点吓人,但效果很好。如果你把这个管理器作为Projectfundingdetail的“objects”属性,你可以像这样获取每个项目的最新资金详情:

>>> Projectfundingdetail.objects.latest_by_project()

这样返回的就是一个普通的查询集,你可以在上面添加更多的过滤条件:

>>> Projectfundingdetail.objects.latest_by_project().filter(budget__lte=1000)

以下是代码:

from django.db import connection, models
qn = connection.ops.quote_name

class ProjectfundingdetailManager(models.Manager):
    def latest_by_project(self):
        project_model = self.model._meta.get_field('project').rel.to

        names = {'project': qn(project_model._meta.db_table),
                 'pfd': qn(self.model._meta.db_table),
                 'end_date': qn(self.model._meta.get_field('end_date').column),
                 'project_id': qn(self.model._meta.get_field('project').column),
                 'pk': qn(self.model._meta.pk.column),
                 'p_pk': qn(project_model._meta.pk.column)}

        sql = """SELECT pfd.%(pk)s FROM %(project)s AS p 
                 JOIN %(pfd)s AS pfd ON p.%(p_pk)s = pfd.%(project_id)s
                 WHERE pfd.%(end_date)s =
                     (SELECT MAX(%(end_date)s) FROM %(pfd)s 
                      WHERE %(project_id)s = p.%(p_pk)s)
              """ % names

        cursor = connection.cursor()
        cursor.execute(sql)
        return self.model.objects.filter(id__in=[r[0] for r
                                                 in cursor.fetchall()])

代码中大约一半(“names”字典)只是为了防止数据库表和列名不标准的情况。如果你确信这些名称不会改变,也可以直接把表名和列名写死在SQL里。

撰写回答