Django ORM 查询限制特定键实例

发布于 2024-07-22 04:33:06 字数 555 浏览 6 评论 0原文

Projectfundingdetail 有一个项目的外键。

以下查询为我提供了 任何 项目资金详细信息低于 1000 的所有项目的列表。如何将其限制为仅最新的项目资金详细信息。

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()

那么我应该使用什么查询来获取所有最新项目资金详细信息低于 1000 的项目。

Projectfundingdetail has a foreign key to project.

The following query gives me the list of all projects that have any projectfundingdetail under 1000. How do I limit it to latest projectfundingdetail only.

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

I have defined the following function,

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

But I cant use the following as latest_funding is not a database field

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

So what query should I use to get all projects that have only their latest projectfundingdetail under 1000.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

乱了心跳 2024-07-29 04:33:06

这个查询比乍一看要难。 AFAIK 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 上的“对象”属性,您可以像这样使用它来获取每个项目的最新资金详细信息:

>>> 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()])

大约一半该代码(“名称”字典)的内容仅需要对非标准数据库表和列名称的可能性具有鲁棒性。 如果您确信表名和列名永远不会改变,您也可以将它们硬编码到 SQL 中。

This query is harder than it looks at first glance. AFAIK the Django ORM does not provide any way to generate efficient SQL for this query, because the efficient SQL requires a correlated subquery. (I'd love to be corrected on this!) You can generate some ugly SQL with this query:

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

But this requires to join from Projectfundingdetail to Project and back again, which is inefficient (though perhaps adequate for your needs).

The other way to do this is to write raw SQL and encapsulate it in a manager method. It looks a little bit scary but works great. If you assign the manager as "objects" attribute on Projectfundingdetail, you can use it like this to get the latest funding details for each project:

>>> Projectfundingdetail.objects.latest_by_project()

And it returns a normal QuerySet, so you can add on further filters:

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

Here's the code:

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()])

About half of that code (the "names" dictionary) is only necessary to be robust against the possibility of nonstandard database table and column names. You could also just hardcode the table and column names into the SQL if you're confident they won't ever change.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文