Django+PostgreSQL:如何基于纯数字手机号实现模糊查询?
解决PostgreSQL中带格式手机号的纯数字片段搜索问题
针对你遇到的手机号搜索需求,这里提供两种可行的解决方案:
方案一:新增纯数字存储字段(推荐,性能最优)
这种方法通过预存纯数字格式的手机号,让查询直接利用数据库索引,效率最高。
1. 修改模型代码
在JobSite模型中添加一个用于存储纯数字手机号的字段,并重写save方法自动生成该字段的值:
import re from django.db import models class JobSite(models.Model): # 原有的字段保持不变... phone_number = models.CharField(max_length=30, null=True, blank=True) # 新增纯数字手机号字段 phone_digits = models.CharField(max_length=20, null=True, blank=True) # 重写save方法自动生成纯数字手机号 def save(self, *args, **kwargs): if self.phone_number: self.phone_digits = re.sub("[^0-9]", "", self.phone_number) else: self.phone_digits = None super().save(*args, **kwargs) # 原有的方法保持不变...
2. 更新视图查询逻辑
在搜索视图中,先将用户输入的搜索词转换为纯数字,再用phone_digits__icontains进行匹配:
import re from django.db.models import Q def search_system(request, search_term=False): if search_term: # 过滤搜索词中的非数字字符 search_term_digits = re.sub("[^0-9]", "", search_term) job_sites = JobSite.objects.filter( Q(first_name__icontains=search_term) | Q(last_name__icontains=search_term) | Q(quickbooks_id__icontains=search_term) | Q(email__icontains=search_term) | # 使用纯数字字段进行模糊匹配 Q(phone_digits__icontains=search_term_digits) ) else: job_sites = JobSite.objects.all().prefetch_related('customer') # 原有的数据组装逻辑保持不变...
注意事项
- 执行
makemigrations和migrate命令,将新增字段同步到数据库。 - 对于已存在的历史数据,可以编写批量更新脚本,调用
phone_digits_only方法生成phone_digits的值。 - 可以给
phone_digits字段添加db_index=True,进一步提升搜索性能。
方案二:实时用PostgreSQL函数处理(无需修改数据库结构)
如果不想新增字段,可以利用PostgreSQL的regexp_replace函数,在查询时实时去除手机号中的非数字字符,再进行匹配。
修改视图查询逻辑
import re from django.db.models import Q, Func, Value from django.db import models def search_system(request, search_term=False): if search_term: search_term_digits = re.sub("[^0-9]", "", search_term) job_sites = JobSite.objects.filter( Q(first_name__icontains=search_term) | Q(last_name__icontains=search_term) | Q(quickbooks_id__icontains=search_term) | Q(email__icontains=search_term) | # 实时转换手机号为纯数字后匹配 Func( 'phone_number', Value(r'[^\d]'), Value(''), function='regexp_replace', output_field=models.CharField() ).__icontains(search_term_digits) ) else: job_sites = JobSite.objects.all().prefetch_related('customer') # 原有的数据组装逻辑保持不变...
替代简化写法(用正则匹配)
也可以直接用PostgreSQL的正则匹配,跳过字符转换步骤:
Q(phone_number__iregex=rf'[^\d]*{re.escape(search_term_digits)}[^\d]*')
这个正则会忽略手机号中的非数字字符,直接匹配用户输入的数字片段。
两种方案对比
- 方案一:性能好,适合数据量大、搜索频繁的场景;需要修改数据库结构,历史数据需要批量更新。
- 方案二:无需修改数据库,实现简单;但每次查询都要实时处理字符串,数据量大时性能会下降。
内容的提问来源于stack exchange,提问作者beach_it
相关产品推荐
相关产品推荐

