Django如何编写查询集获取查找表中每个客户的主电话号码
解决方案
首先先修正你现有代码里的基础问题:
- 模型关联写反了:单个客户可对应多条电话号码记录,外键应该放在
Phone模型侧指向Client,你当前在Client模型上定义phone外键的写法,相当于强制一个客户只能绑定1个电话,和你"多号码+主号标记"的表结构完全冲突 - 你写的
Phone模型里client_id、type_id、is_primary字段都没有加括号实例化,代码跑起来会直接报错 - 所有关联字段加
db_column参数映射现有表字段,不需要修改你已经建好的数据库表结构
第一步:修正模型定义
修正后的三个模型代码如下,完全兼容你给出的表结构:
class Client(models.Model): id = models.IntegerField(primary_key=True) last = models.CharField(max_length=32) first = models.CharField(max_length=32) status_id = models.SmallIntegerField() # 补全你查询时用到的status_id字段,避免filter报错 class Meta: db_table = 'client' # 按你实际的客户表名调整即可 class PhoneType(models.Model): id = models.SmallIntegerField(primary_key=True) type = models.CharField(max_length=16, blank=False, null=False) class Meta: db_table = 'phone_type' class Phone(models.Model): id = models.IntegerField(primary_key=True) client = models.ForeignKey( Client, on_delete=models.PROTECT, related_name='phones', db_column='client_id' # 映射数据库中已有的client_id字段 ) type = models.ForeignKey( PhoneType, on_delete=models.PROTECT, blank=False, null=False, db_column='type_id' # 映射数据库中已有的type_id字段 ) is_primary = models.BooleanField(db_column='is_primary') country_code = models.CharField(max_length=5) phone_number = models.CharField(max_length=16, db_column='number') # 映射你表中存号码的number字段 class Meta: db_table = 'phones'
第二步:修改视图查询集
用Django内置的Prefetch对象做预加载,查询客户列表的时候一次性把所有关联的主号码拉出来,避免循环查询的N+1性能问题,修改后的ClientListView代码:
from django.views.generic import ListView from django.db.models import Prefetch from .models import Client, Phone class ClientListView(ListView): model = Client template_name = 'client/client_list.html' context_object_name = 'clients' def get_queryset(self): # 预加载规则:只取主号码,同时关联查出电话类型,单独挂到Client的primary_phone属性上 primary_phone_prefetch = Prefetch( lookup='phones', queryset=Phone.objects.filter(is_primary=True).select_related('type'), to_attr='primary_phone' ) return Client.objects.filter( status_id=3 ).order_by( '-id' ).prefetch_related( primary_phone_prefetch )
这个写法最终只会执行2条SQL:一条查询符合条件的客户列表,一条批量查询这些客户绑定的主号码,性能远优于循环单查。
模板使用方式
因为预加载时用了to_attr,主号码会直接存在每个客户实例的primary_phone属性上,这是一个单元素列表(对应唯一主号),模板中直接取值即可:
<table> <thead> <tr> <th>客户ID</th> <th>姓名</th> <th>主号码</th> <th>号码类型</th> </tr> </thead> <tbody> {% for client in clients %} <tr> <td>{{ client.id }}</td> <td>{{ client.last }} {{ client.first }}</td> <td>{{ client.primary_phone.0.phone_number|default:"未登记主号码" }}</td> <td>{{ client.primary_phone.0.type.type|default:"-" }}</td> </tr> {% endfor %} </tbody> </table>
- 如果业务规则保证每个客户必定有一个主号码,可以去掉
default过滤器;如果存在无主号的客户,default会自动显示兜底文本,不会抛模板错误。
内容的提问来源于stack exchange,提问作者Carlo Navici
相关产品推荐
相关产品推荐

