如何用Django ORM获取所有外键关联数据(无需循环)
Django ORM 批量查询关联数据(无ORM循环)
我来帮你搞定这个问题,不需要在ORM层面做循环就能拿到你想要的结构化结果,下面一步步来梳理:
你的现有模型与需求
模型定义
class A(models.Model): name = models.CharField(max_length=1000, unique=True) class B(models.Model): name = models.CharField(max_length=1000, unique=True) a = models.ForeignKey(A, on_delete=models.PROTECT, related_name="a")
期望结果格式
[{ "name":"a", "a":[{ "name":"a1"}, { "name":"b1"}, { "name":"c1"}] }, { "name":"b", "b":[{ "name":"a2"}] }]
示例数据表数据
表A
| id | name |
|---|---|
| 1 | a |
| 2 | b |
表B
| id | name | a |
|---|---|---|
| 1 | a1 | 1 |
| 2 | b1 | 1 |
| 3 | c1 | 1 |
| 4 | a2 | 2 |
你已经能实现单条A记录的关联查询:
a_ob = A.objects.get(id=1) b_ob = a_ob.a.all()
现在需要批量获取所有A记录的对应格式结果,且避免ORM循环。
三种可行解决方案
方法一:prefetch_related + Python列表推导(轻量高效)
这个方法用prefetch_related一次性加载所有A和关联的B数据(仅2次SQL查询,避免N+1问题),然后用Python列表推导来构造格式——这里的遍历是Python层面的,不是ORM循环,性能很好:
from django.db.models import Prefetch # 提前预加载所有A及其关联的B数据,只取name字段 a_queryset = A.objects.prefetch_related( Prefetch('a', queryset=B.objects.only('name')) ).all() # 构造目标格式 result = [ { 'name': a.name, a.name: [{'name': b.name} for b in a.a] } for a in a_queryset ]
方法二:DRF序列化器(优雅适配API场景)
如果你的项目用了Django REST Framework,用序列化器来处理会更优雅,可读性和可维护性都更强:
from rest_framework import serializers # 先定义B的序列化器,只返回name字段 class BSerializer(serializers.ModelSerializer): class Meta: model = B fields = ('name',) # 定义A的序列化器,动态生成以A.name为键的关联字段 class ASerializer(serializers.ModelSerializer): def to_representation(self, instance): # 先获取基础的name字段数据 data = super().to_representation(instance) # 序列化关联的B数据 b_serialized = BSerializer(instance.a.all(), many=True).data # 添加以A的name为键的字段 data[instance.name] = b_serialized return data class Meta: model = A fields = ('name',) # 优化查询,预加载关联数据避免N+1 a_queryset = A.objects.prefetch_related('a').all() # 序列化所有A对象 serializer = ASerializer(a_queryset, many=True) result = serializer.data
方法三:PostgreSQL专属的JSON聚合(数据库层面完成)
如果你的数据库是PostgreSQL,可以直接用Django的聚合函数在数据库层面生成结构化数据,性能最优:
from django.db.models import Func, Value from django.contrib.postgres.aggregates import JSONAgg # 自定义函数,生成{"name": xxx}格式的JSON对象 class BuildNameObject(Func): function = 'json_build_object' template = "%(function)s('name', %(expressions)s)" # 聚合查询,直接在数据库层面生成关联的B数据数组 a_aggregated = A.objects.annotate( b_list=JSONAgg(BuildNameObject('a__name')) ).values('name', 'b_list') # 转换为目标格式 result = [ {'name': item['name'], item['name']: item['b_list']} for item in a_aggregated ]
内容的提问来源于stack exchange,提问作者Shyamala Gopalakrishnan
相关产品推荐
相关产品推荐

