You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 09:54:18