如何在Django多对多关系模型中注解获取关联餐厅名称字符串
问题
我有两个Django模型,Restaurant和RestaurantCategory,二者是多对多关系,模型代码如下:
class RestaurantCategory(BaseModel): name = models.CharField(max_length=150) description = models.TextField() class Restaurant(BaseModel): name = models.CharField(db_index=True, max_length=128) is_partner = models.BooleanField(default=True) category = models.ManyToManyField( RestaurantCategory, related_name="restaurant_categories", blank=True, db_index=True, )
我需要从RestaurantCategory模型查询时通过注解新增一个restaurant_names字段,值为所有关联餐厅名称拼接成的单个字符串,预期代码结构如下:
new_data = RestaurantCategory.objects.all().annotate( restaurant_names=<包含所有关联餐厅名称的字符串> )
请问是否可以通过注解实现该需求?若不能,有哪些可行的替代方案?
解答
可以通过注解实现(依赖数据库支持)
如果你的项目使用PostgreSQL,Django 1.11+支持StringAgg聚合函数,直接用它就能在数据库层面完成字符串拼接:
from django.db.models import StringAgg new_data = RestaurantCategory.objects.all().annotate( restaurant_names=StringAgg('restaurant_categories__name', delimiter=', ') )
如果需要过滤关联的餐厅(比如只取合作餐厅),可以搭配FilteredRelation:
from django.db.models import StringAgg, FilteredRelation, Q new_data = RestaurantCategory.objects.annotate( partner_restaurants=FilteredRelation( 'restaurant_categories', condition=Q(restaurant_categories__is_partner=True) ) ).annotate( restaurant_names=StringAgg('partner_restaurants__name', delimiter=', ') )
如果使用MySQL,可以用GroupConcat(需要先安装django-mysql库):
from django_mysql.models import GroupConcat new_data = RestaurantCategory.objects.all().annotate( restaurant_names=GroupConcat('restaurant_categories__name', separator=', ') )
替代方案(跨数据库兼容)
如果你的项目需要跨数据库兼容,或者无法使用上述聚合函数,可以选择以下方式:
- 批量查询后拼接
先预取所有关联餐厅避免N+1查询,再在Python层面完成字符串拼接:
from django.db.models import Prefetch # 预取仅包含名称的关联餐厅数据 categories = RestaurantCategory.objects.prefetch_related( Prefetch('restaurant_categories', queryset=Restaurant.objects.only('name')) ) # 循环拼接名称 for category in categories: category.restaurant_names = ', '.join([r.name for r in category.restaurant_categories.all()])
- 模型方法
在RestaurantCategory模型中定义方法,动态返回拼接后的名称:
class RestaurantCategory(BaseModel): name = models.CharField(max_length=150) description = models.TextField() def get_restaurant_names(self): return ', '.join([r.name for r in self.restaurant_categories.all()])
使用时直接调用category.get_restaurant_names()即可,搭配prefetch_related可优化查询性能。
- 自定义SQL注解
通过RawSQL执行原生SQL完成拼接,以MySQL为例:
from django.db.models import RawSQL new_data = RestaurantCategory.objects.annotate( restaurant_names=RawSQL( "SELECT GROUP_CONCAT(r.name SEPARATOR ', ') FROM restaurant_restaurant r " "JOIN restaurant_restaurant_category rc ON r.id = rc.restaurant_id " "WHERE rc.restaurantcategory_id = restaurant_restaurantcategory.id", [] ) )
注意替换SQL中的表名为你项目实际生成的数据库表名(Django默认规则是应用名_模型名)。
内容的提问来源于stack exchange,提问作者Theesh
相关产品推荐
相关产品推荐

