Case/When
条件表达式让你可以在过滤器、注解、聚合和更新中使用 if … elif … else 逻辑。条件表达式对表的每条记录执行一系列条件,并返回匹配的结果表达式。
假设有如下需求”年龄小于18的为未成年(1),年龄在30之间的为青年(2),年龄在60的为中年(3),其他为老年(0)“,那么使用Case/When方法再配合annotate()方法就可以优雅得实现功能。
Staff.objects.filter().annotate(
age_tag = Case(
When(
age__lt=18,
then=1,
),
When(
age__lt=30,
then=2,
)
When(
age__lt=60,
then=3,
)
default=0,
output_field=IntegerField(),
)
)
if age < 18:
age_tag = 1
elif age < 30:
age_tag = 2
elif age < 60:
age_tag = 3
else:
age_tag = 0
示例二: 统计有多少重复名字的同学。
counts = []
q = CityA.objects.values('state_id').annotate(
jiu_count=Count(Case(When(name='Chicago9999', then=0))),
chica_count=Count(Case(When(name='Chicago', then=0))),
)
print(q)
for i in q:
counts.append({
"jiu_count": i.get('jiu_count'),
"chica_count": i.get('chica_count'),
})
print(counts)
分组之后可以在查询的任何位置使用 filter,仅获取状态为活跃的用户:
q = CityA.objects.values('state_id').filter(status=True).annotate(
jiu_count=Count(Case(When(name='Chicago9999', then=0))),
chica_count=Count(Case(When(name='Chicago', then=0))),
)
在 django 2.0 以后也可以这样写: Count新加入了一个参数filter,使用它,可以在计算count数量之前先对集合进行过滤
q = State.objects.annotate(
cities_count=Count('cities', filter=Q(cities__population__gt=20000))
).first()
Coalesce
在 django 使用统计 annotate 和 aggregate 时由于没有数据导致统计是 null , 而不是 0. 这对返回结果产生很大影响, 可以使用 Coalesce 方法来设置默认值。
q = CityA.objects.aggregate(
age_count=Sum('age')
)
print(q)
使用 Coalesce:
q = CityA.objects.aggregate(
age_count=Coalesce(Sum('age'), 0)
)
print(q)
聚合
聚合分两种:
- 从整个 QuerySet 生成汇总值。
- 为 QuerySet 中的每一个 object 生成聚合。
aggregate 从整个 QuerySet 生成汇总值: 聚合函数在 django 2.0 以后新加入了一个参数filter, 所有聚合函数都可以使用 filter 方法, 需要用 Q 包裹起来。
q = CityA.objects.aggregate(
age_count=Coalesce(Max('population', filter=Q(status=True)), 0)
)
q = CityA.objects.aggregate(
age_count=Coalesce(Min('population', filter=Q(status=True)), 0)
)
q = CityA.objects.aggregate(
age_count=Coalesce(Avg('population', filter=Q(status=True)), 0)
)
q = CityA.objects.aggregate(
age_count=Coalesce(Sum('population', filter=Q(status=True)), 0)
)
q = CityA.objects.aggregate(
age_count=Coalesce(Count('population', filter=Q(status=True)), 0)
)
print(q)
为 QuerySet 中的每一个 object 生成聚合: 使用 annotate() 子句可以生成每一个对象的汇总。当指定 annotate() 子句,QuerySet 中的每一个对象将对指定值进行汇总。 计算每个国家有多少城市:
q = State.objects.annotate(
count_total=Count('cities')
)
print(q.values('count_total'))
这里也是反向聚合, 因为我从 State 表中查询,cities 是反向 relate_name 的名称, 表结构如下:
class State(models.Model):
objects = StateQuerySet.as_manager()
name = models.CharField(verbose_name="名称", max_length=64)
class CityA(models.Model):
objects = CityAQuerySet.as_manager()
state = models.ForeignKey(State, on_delete=models.CASCADE, related_name='cities')
name = models.TextField()
population = models.IntegerField()
land_area_km = models.IntegerField()
age = models.PositiveIntegerField(default=None, null=True)
status = models.BooleanField(default=False)
聚合也可以参与过滤:
q = State.objects.filter(cities__status=True).annotate(
count_total=Count('cities')
)
print(q.values('count_total'))
当使用 aggregate() 子句,过滤器将具有约束计算聚合的对象的效果。
q = State.objects.filter(cities__status=True).aggregate(total=Sum('cities__land_area_km'))
print(q)
order_by(), 注解可以当做基本排序来使用:
q = State.objects.annotate(
count_total=Count('cities')
).order_by('count_total')
annotate() 和 values() 的顺序: 和使用 filter() 一样,作用于某个查询的 annotate() 和 values() 子句的顺序非常重要。如果 values() 子句在 annotate() 之前,就会根据 values() 子句产生的分组来计算注解。 下面是一个关于国家的查询例子,查询每个国家的总面积公里:
q = CityA.objects.values('state_id').annotate(total=Sum('land_area_km'))
print(q)
可以在注解结果上生成聚合:
查询每个国家有多少城市,在计算平均值
q = State.objects.annotate(total=Count('cities')).aggregate(avg=Avg('total'))
print(q)
不要再 for 循环执行 sql 操作
start_at = time.time()
q = State.objects.annotate(
cities_count=Count(Case(
When(
cities__state_id=1,
then=0
),
default=0,
output_field=IntegerField())
)
)
print(q.values('cities_count'))
end_at = time.time()
print(end_at - start_at)
s_at = time.time()
qs = State.objects.all()
for k in qs:
print(k.cities.all().count())
e_at = time.time()
print(e_at - s_at)
参考文献:
聚合:https://docs.djangoproject.com/zh-hans/3.2/topics/db/aggregation/
Case/When:https://docs.djangoproject.com/zh-hans/3.2/ref/models/conditional-expressions/
Coalesce: https://docs.djangoproject.com/zh-hans/3.1/ref/models/database-functions/#coalesce
|