Django:无需循环高效获取每个分类下6个产品的方法
嘿,这个问题我之前也碰到过,原来的循环写法确实会触发太多数据库查询,尤其是分类多的时候效率很低。给你两个高效的方案,都不用显式循环,而且能大幅减少查询次数:
首先先贴一下你的模型和原来的实现,方便对照:
你的模型代码
class Category(models.Model): name = models.CharField(max_length=120, blank=True, null=True, default=None) class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.CASCADE) # 实际项目里别忘了加on_delete参数
原来的实现
products = [Product.objects.filter(category_id=i.id)[:6] for i in Category.objects.all()]
方案一:用Prefetch对象(推荐,最简洁)
Django的Prefetch可以让我们自定义预取关联数据的逻辑,包括限制数量。这样只需要两次查询(一次查分类,一次查所有符合条件的产品),Django会自动把产品关联到对应的分类上:
from django.db.models import Prefetch # 定义预取规则:每个分类只取前6个产品,存到top_products属性里 prefetch_top_products = Prefetch( 'product_set', # 这里是Category到Product的反向关联名,默认是`小写模型名_set`,如果加了related_name就用那个 queryset=Product.objects.order_by('-created_at')[:6], # 可以按你需要的字段排序,比如最新创建的在前 to_attr='top_products' ) # 一次性获取所有分类和对应的前6个产品 categories = Category.objects.prefetch_related(prefetch_top_products).all() # 之后使用的时候,直接访问每个分类的top_products属性就行 for category in categories: print(f"分类{category.name}的前6个产品:{category.top_products}")
这个方法的好处是代码简洁,而且Django会帮你处理好关联,不需要手动映射,查询次数最少。
方案二:用Subquery子查询
如果你需要更灵活的控制,可以用Subquery来在Category的查询中直接获取每个分类的产品ID列表,之后再一次性查询产品实例:
from django.db.models import Subquery, OuterRef # 定义子查询:获取每个分类下的前6个产品ID top_product_ids = Product.objects.filter( category=OuterRef('pk') ).order_by('id')[:6].values_list('pk', flat=True) # 查询所有分类,并附带各自的前6个产品ID categories = Category.objects.annotate( top_product_ids=Subquery(top_product_ids) ).all() # 一次性获取所有相关产品,避免多次查询 all_product_ids = [] for cat in categories: all_product_ids.extend(cat.top_product_ids) products_map = {p.pk: p for p in Product.objects.filter(pk__in=all_product_ids)} # 把产品实例映射到分类上 for cat in categories: cat.top_products = [products_map[pk] for pk in cat.top_product_ids]
这个方法适合需要对产品ID做额外处理的场景,总共3次查询,还是比原来的N+1高效很多。
总结一下,这两种方法都避免了显式循环查询,大幅减少了数据库交互次数,尤其是分类数量多的时候,性能提升会非常明显。
内容的提问来源于stack exchange,提问作者alex
相关产品推荐
相关产品推荐

