如何用Django ORM计算多时间段内指定品牌的市场份额?
解决方案:使用Django ORM计算品牌市场份额
方案一:使用CTE实现高效关联查询(推荐,需Django 3.2+)
该方案生成的SQL逻辑与你期望的一致,利用CTE替代子查询实现高效关联,同时保留ORM动态添加过滤条件的灵活性。
from django.db.models import Sum, F from django.db.models.functions import Coalesce # 创建CTE,计算每个时间段的全品牌总销售额 market_total_cte = MarketData.objects.values('time_period')\ .annotate(market_total=Sum('sales'))\ .with_cte('market_totals') # 定义过滤条件(可动态添加更多WHERE子句) filtered_data = MarketData.objects.filter( brand__in=['Brand 1', 'Brand 2'], time_period__range=('2020-01-01', '2023-03-31') # 示例:添加品类过滤 -> category='Electronics' ) # 关联CTE并计算市场份额 result = market_total_cte.join( filtered_data, time_period=F('market_totals__time_period') ).values( 'time_period', 'brand', 'market_totals__market_total' ).annotate( brand_sales=Sum('sales') ).annotate( # 使用Coalesce避免除以0的异常 share=Coalesce(F('brand_sales') / F('market_totals__market_total'), 0.0) ).order_by('brand', 'time_period')
方案二:优化后的Subquery方案(兼容旧版Django)
若无法升级Django版本,可使用该方案,配合索引优化后性能可接近CTE方案。
from django.db.models import Sum, Subquery, OuterRef, F, IntegerField from django.db.models.functions import Coalesce # 子查询获取每个时间段的全品牌总销售额(限制返回单条结果) total_sales_subquery = MarketData.objects.filter( time_period=OuterRef('time_period') ).values('time_period').annotate( total=Sum('sales') ).values('total')[:1] # 主查询 result = MarketData.objects.filter( brand__in=['Brand 1', 'Brand 2'], time_period__range=('2020-01-01', '2023-03-31') ).values('time_period', 'brand').annotate( brand_sales=Sum('sales'), market_total=Subquery(total_sales_subquery, output_field=IntegerField()) ).annotate( share=Coalesce(F('brand_sales') / F('market_total'), 0.0) ).order_by('brand', 'time_period')
性能优化建议
为大幅提升查询速度,需在MarketData模型中添加索引:
class MarketData(models.Model): category = models.CharField(max_length=64, null=True) brand = models.CharField(max_length=128, null=True) time_period = models.DateField(db_index=True) sales = models.IntegerField(default=0, null=True) class Meta: indexes = [ # 优化时间段分组查询 models.Index(fields=['time_period']), # 优化品牌+时间段的过滤与分组 models.Index(fields=['brand', 'time_period']), ]
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

