Django 之提升查询性能(二)

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)
# {'avg': 3334.3333}

不要再 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()   
# 如果这里有 100 万条数据,就是循环 100 万次,才能得出最终结果。对于一个接口来说,数据库操作次数越少越好,查这么多次简直是一个天文数字。
# 如果只有 50 万条数据是有效的, 那就回浪费 %50 的查询效率,对性能造成的影响是巨大的。
for k in qs:
    print(k.cities.all().count())
e_at = time.time()
print(e_at - s_at)

# 150 条数据,差别已经到秒级了, 如果数据量大的话那就非常糟糕了
# 0.0009999275207519531   // 使用 annotate
# 0.23205280303955078  // for 循环

参考文献:

聚合: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

Guess you like

Origin blog.csdn.net/qq_39253370/article/details/119877844