You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 14:48:33