如何用Django ORM实现含图片聚合的多表关联查询
问题描述
现有以下Django模型:
class Mark(models.Model): name = models.CharField(max_length=200, verbose_name="Марка", unique=True, null=True) #... class Model(models.Model): mark = models.ForeignKey(Mark, on_delete=models.CASCADE, verbose_name="Марка") name = models.CharField(max_length=200, verbose_name="Модель", unique=True, null=True) #... class Car(models.Model): vin = models.CharField(max_length=100, null=True) #... class Image(models.Model): car = models.ForeignKey(Car, related_name='images', on_delete=models.CASCADE) image = models.ImageField(upload_to=get_upload_path, verbose_name="Фото") #...
需要用Django ORM替代如下原生SQL查询(禁止使用raw方法):
query = "SELECT c.*, m.name mark_name, JSON_ARRAYAGG(img.image) image FROM incarcatalog_car c \ LEFT JOIN incarcatalog_mark m ON m.id=c.mark_id \ LEFT JOIN incarcatalog_image img ON img.car_id=c.id \ WHERE c.id>%s \ GROUP BY c.id;" queryset = Car.objects.raw(query, [id])
目前已通过queryset = Car.objects.select_related("mark", "model").all()实现Mark、Model表的左关联,但不知道如何关联Image表并实现图片聚合。
解决方案
要实现原生SQL中的图片聚合效果,结合annotate()和Django聚合函数即可完成,具体实现分版本处理:
Django 3.1及以上版本(支持JSON数组聚合)
- 先导入所需模块:
from django.db.models import F, Value from django.db.models.functions import JSONArrayAgg
- 构造ORM查询:
# 替换your_id_value为实际的id阈值 queryset = Car.objects.filter(id__gt=your_id_value) \ .select_related('mark') \ .annotate( mark_name=F('mark__name'), image=JSONArrayAgg(F('images__image'), default=Value('[]')) )
关键说明
filter(id__gt=your_id_value)对应原生SQL的WHERE c.id>%s条件;select_related('mark')维持左关联Mark表的逻辑,避免N+1查询;annotate(mark_name=F('mark__name'))将Mark表的name字段别名成mark_name,对应原生SQL的m.name mark_name;JSONArrayAgg(F('images__image'), default=Value('[]'))实现JSON_ARRAYAGG(img.image)的聚合效果,default参数确保无图片的Car对象返回空数组,和原生LEFT JOIN行为一致;- 通过
images__image利用模型中定义的related_name关联到Image表的image字段。
Django 3.1以下版本(使用普通数组聚合)
如果版本不支持JSONArrayAgg,可以用ArrayAgg替代,返回普通数组而非JSON格式:
from django.db.models import ArrayAgg, F, Value queryset = Car.objects.filter(id__gt=your_id_value) \ .select_related('mark') \ .annotate( mark_name=F('mark__name'), image=ArrayAgg(F('images__image'), default=Value([])) )
内容的提问来源于stack exchange,提问作者Aleksej Babushkin
相关产品推荐
相关产品推荐

