通过数据库别名来优化查询.
需求场景: 结合前端做统一的下拉,比如要单位数据(id, title)下拉表单.部门下拉数据(id, name)等等,这类都是从一个表中提取一两个字段给前端.那么前端为了使用方便,可能会要求说,后端给我统一返回(id, select_show),前端就可以使用同一个组件进行展示了.
我们通常的情况就是先直接从数据库查询出来前端需要的数据,然后再for循环统一处理:
from django.db import models
from django.db.models import F
# 公司/单位管理表
class Company(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class Meta:
verbose_name = '公司单位管理表'
verbose_name_plural = '公司单位管理'
indexes = [
# 联合索引,提高select_show的效率
models.Index(fields=['id', 'name'], name='idx_company_id_name'),
]
# 返回执行单位的下拉菜单数据.
class CompanySelectShowViewSet(GenericReadOnlyListModelViewSet):
queryset = Company.objects.all()
# serializer_class = CompanySerializer
pagination_class = None
authentication_classes = [JwtAuthentication, JwtParamAuthentication, DenyAuthentication]
def list(self, request, *args, **kwargs):
# --------- 方案1 ---------
# 需要先查出来,再进行循环处理为前端需要的数据格式,这里需要两部步处理
# queryset = Company.objects.values('id', 'name')
# # SELECT `rbac_company`.`id`, `rbac_company`.`name` FROM `rbac_company`
# data = [{"id": i['id'], "select_show": i['name']} for i in queryset]
# return Response({"code": 0, "detail": "success","data": data})
# --------- 方案2 ---------
# 直接从数据库层面进行别名操作,性能更佳,不需要再进行循环处理
data = Company.objects.annotate(select_show=F('name')).values('id', 'select_show')
# SELECT `rbac_company`.`id`, `rbac_company`.`name` AS `select_show` FROM `rbac_company`
# QuerySet 转 list 即可
return Response({"code": 0, "detail": "success","data": list(data)})
# 上面的方案2还可以进行优化,下面的示例来自于另一个接口,基本原理是一样的.
# 实现思路就是在返回基本的id和select_show这两个字段的基础上
# 也可以通过请求参数来额外的获取表中的其它字段值
class SubProjectSelectShowViewSet(GenericReadOnlyListModelViewSet):
queryset = SubProject.objects.all()
pagination_class = None
authentication_classes = [JwtAuthentication, JwtParamAuthentication, DenyAuthentication]
# 允许哪些额外的透传字段
ALLOWED_EXTRA_FIELDS = {
f.name for f in SubProject._meta.get_fields()
if isinstance(f, (models.CharField, models.IntegerField, models.ForeignKey, models.BooleanField))
and f.name not in {'xxx', 'ooo'} # 排除敏感字段
}
def list(self, request, *args, **kwargs):
# 获取客户端请求的额外字段
requested_fields = set(request.query_params.keys())
# 过滤:只保留白名单中的字段
extra_fields = requested_fields & self.ALLOWED_EXTRA_FIELDS # 集合交集
# 可选:记录非法字段(用于监控或告警)
illegal_fields = requested_fields - self.ALLOWED_EXTRA_FIELDS
if illegal_fields:
# 可选:记录日志,但不报错(静默忽略)
logger.warning(f"[验工计价] 请求子课题下拉列表接口,传递了错误的请求参数: {illegal_fields} from {request.META.get('REMOTE_ADDR')}")
# 构建 values 字段列表
fields_to_select = ['id', 'select_show']
fields_to_select.extend(extra_fields)
data = SubProject.objects.annotate(
select_show=F('name')
).values(*fields_to_select)
return Response({"code": 0, "detail": "success", "data": list(data)})
"""
请求示例:
/api/tunnelaccept/subproject/select/show/
返回:{"id": 317,"select_show": "2026-外部课题-子课题2"}
/api/tunnelaccept/subproject/select/show/?executing_units=1
返回:{"id": 317, "executing_units": 5,"select_show": "2026-外部课题-子课题2"}
/api/tunnelaccept/subproject/select/show/?parent_project=1&executing_units=1
返回:{"id": 317, "executing_units": 5,"select_show": "2026-外部课题-子课题2", "parent_project":163}
"""
如果被查询的表不是频繁改动的,可以通过结合redis进一步实现接口缓存,将性能优化做到极致.