在单个查询中获取外键对象

12 投票
4 回答
18199 浏览
提问于 2025-04-15 22:42

我在Django代码里有两个模型:

class ModelA(models.Model):
    name = models.CharField(max_length=255)
    description = models.CharField(max_length=255)
    created_by = models.ForeignKey(User)

class ModelB(models.Model):
    category = models.CharField(max_length=255)
    modela_link = models.ForeignKey(ModelA, 'modelb_link')
    functions = models.CharField(max_length=255)
    created_by = models.ForeignKey(User)

假设ModelA有100条记录,而这些记录可能有链接到ModelB,也可能没有。

现在我想要获取每一条ModelA记录,以及对应的ModelB的数据。

我会这样做:

list_a = ModelA.objects.all()

然后为了获取ModelB的数据,我需要这样做:

for i in list_a:
    i.additional_data = i.modelb_link.all()

不过,这样做会对每一个i的实例都运行一次查询。这样总共就要运行101次查询。

有没有办法只用一次查询就能完成这个?或者至少少于101次查询。

我试过使用 ModelA.objects.select_related().all(),但似乎没有什么效果。

4 个回答

1

之所以 .select_related() 没有起作用,是因为 .select_related() 是用来跟踪外键的。你的 ModelA 并没有指向 ModelB 的外键,而是 ModelB 有一个指向 ModelA 的外键。(这意味着一个 ModelA 实例可以关联多个 ModelB 实例)。

你可以用两条查询和一些 Python 代码来实现这个功能:

list_b = ModelB.objects.all()
list_a = ModelA.objects.all()
for a in list_a:
    a.additional_data = [b for b in list_b if b.modela_link_id==a.id]
1

Django的ORM(对象关系映射)是个不错的工具,但有些事情手动做会更好。你可以导入连接的游标,然后在一次查询中执行原始的SQL语句。

from django.db import connection
cur=connection.cursor()
cur.execute(query)
rows = cur.fetchall()

你的查询应该像这样(针对MySQL

SELECT * FROM appname_modela INNER JOIN appname_modelb ON appname_modela.id=appname_modelb.modela_link_id
7

正如Ofri所说,select_related 只适用于正向关系,而不适用于反向关系。

Django没有内置的方法可以自动处理反向关系,但你可以参考我在博客上的文章,里面介绍了一种相对高效的做法。基本的思路是一次性获取每个项目的所有相关对象,然后手动将它们与相关项目关联起来,这样你就可以用2个查询来完成,而不是n+1个查询。

撰写回答