如何基于多对多关系存在性排序(Django披萨示例)
单条Django查询实现配料排序与标记需求
问题背景
Django模型定义
class Topping(models.Model): order = models.IntegerField() name = models.CharField(max_length=250) class Pizza(models.Model): toppings = models.ManyToManyField(Topping)
已创建的配料实例
Topping.objects.create(order=1, name="tomato") Topping.objects.create(order=2, name="cheese") Topping.objects.create(order=3, name="olives") Topping.objects.create(order=4, name="salami") Topping.objects.create(order=5, name="onion") Topping.objects.create(order=6, name="rocket")
需求说明
现有一个包含tomato、cheese和salami的披萨,需要获取所有Topping数据:
- 排序规则:优先按披萨是否拥有该配料排序(拥有的在前),再按
order字段升序排列 - 每个配料需标记是否被披萨拥有,期望结果结构如下:
[ {"id": 1, "name": "tomato", "has_topping": True}, {"id": 2, "name": "cheese", "has_topping": True}, {"id": 4, "name": "salami", "has_topping": True}, {"id": 3, "name": "olives", "has_topping": False}, {"id": 5, "name": "onion", "has_topping": False}, {"id": 6, "name": "rocket", "has_topping": False}, ]
(注:原示例中salami和olives的ID存在笔误,已修正)
解决方案:单条数据库查询实现
可以通过Django ORM的annotate结合Case/When表达式,在单条查询中完成标记和排序逻辑,无需分两次查询再用Python处理。
实现代码
# 先获取目标披萨实例(根据实际场景替换获取方式) pizza = Pizza.objects.get(...) # 例如:Pizza.objects.filter(toppings__name='tomato').first() # 单条查询完成需求 toppings_queryset = Topping.objects.annotate( has_topping=Case( When(id__in=pizza.toppings.values_list('id', flat=True), then=Value(True)), default=Value(False), output_field=BooleanField() ) ).order_by('-has_topping', 'order') # 转换为期望的字典结构 result = [ {"id": topping.id, "name": topping.name, "has_topping": topping.has_topping} for topping in toppings_queryset ]
代码说明
- 标记
has_topping:通过annotate添加临时字段has_topping,使用Case/When判断当前配料ID是否在目标披萨的配料ID列表中,存在则返回True,否则返回False。 - 排序规则:
order_by('-has_topping', 'order')先按has_topping降序排列(True值排在False前面),再按order字段升序排列,完全匹配需求。 - 性能优势:所有逻辑在数据库层面完成,仅需一次查询,避免了多次查询后用Python处理数据的额外开销。
内容的提问来源于stack exchange,提问作者run_the_race
相关产品推荐
相关产品推荐

