如何用Django ORM高效查询嵌套多对多表,获取带食谱的烹饪步骤
问题描述
我设计了如下Django烹饪会话相关模型:
from django.db import models class Recipe(models.Model): name = models.CharField(max_length=255) def __str__(self): return self.name class Step(models.Model): name = models.CharField(max_length=255) recipes = models.ManyToManyField(Recipe, related_name='steps') def __str__(self): return self.name class CookingSession(models.Model): name = models.CharField(max_length=255) steps = models.ManyToManyField(Step, related_name='cooking_sessions') def __str__(self): return self.name
当前使用如下查询:
cooking_sessions = ( CookingSession.objects.annotate( step_list=ArrayAgg( models.F( "steps__name", ), distinct=True, ), recipe_list=ArrayAgg(models.F("steps__recipes__name")), ) )
得到的数据格式为:
[ { 'id': 1, 'name': 'Italian Night', 'step_list': ['Preparation', 'Cooking', 'Serving'], 'recipe_list': ['Tomato Sauce', 'Pasta Dough', 'Spaghetti', 'Tomato Sauce', 'Garlic Bread'] }, ... ]
希望以最少的查询次数(最好仅1次)获取特定烹饪会话的所有步骤,且每个步骤包含对应的食谱,期望的数据格式如下:
{ 'id': 1, 'name': 'Italian Night', 'steps': [ { 'step_name': 'Preparation', 'recipes': ['Tomato Sauce', 'Pasta Dough'] }, { 'step_name': 'Cooking', 'recipes': ['Spaghetti', 'Tomato Sauce'] }, { 'step_name': 'Serving', 'recipes': ['Garlic Bread'] } ] }
请问该如何实现?
解决方案
可以通过两种方式实现单次查询获取目标格式数据,以下是具体方案:
方案1:纯ORM聚合实现(单次数据库查询)
利用嵌套ArrayAgg和Subquery,为每个步骤聚合对应的食谱列表,再将结构化的步骤数据聚合到烹饪会话中:
from django.db import models from django.db.models import Subquery, OuterRef, ArrayAgg # 子查询:为每个步骤生成对应的食谱名称列表 step_recipes_subquery = Step.objects.filter( id=OuterRef('steps__id') ).annotate( recipe_names=ArrayAgg('recipes__name', distinct=True) ).values('name', 'recipe_names') # 查询特定烹饪会话,聚合步骤及对应食谱 target_session = CookingSession.objects.filter(id=1).annotate( steps=ArrayAgg( models.Value( { 'step_name': models.F('steps__name'), 'recipes': Subquery(step_recipes_subquery.values('recipe_names')[:1]) }, output_field=models.JSONField() ), distinct=True ) ).values('id', 'name', 'steps').first()
该方案全程仅1次数据库查询,直接返回符合要求的结构化数据,需要Django 3.1+版本支持JSONField和嵌套聚合语法。
方案2:Prefetch预加载+Python整理(单次数据库查询)
如果觉得嵌套聚合写法复杂,可通过Prefetch预加载关联数据,再在Python层面整理成目标格式:
from django.db.models import Prefetch # 预加载步骤时同时预加载关联的食谱,避免N+1查询 session = CookingSession.objects.filter(id=1).prefetch_related( Prefetch('steps', queryset=Step.objects.prefetch_related('recipes')) ).first() # 整理成目标格式 result = { 'id': session.id, 'name': session.name, 'steps': [ { 'step_name': step.name, 'recipes': [r.name for r in step.recipes.all()] } for step in session.steps.all() ] }
这种方式数据库仅执行1次查询,Python层面的整理逻辑直观易懂,适合对ORM嵌套聚合不太熟悉的场景。
内容的提问来源于stack exchange,提问作者Dante
相关产品推荐
相关产品推荐

