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

Django+PostgreSQL多列无重音大小写不敏感模糊查询需求

解决Django+PostgreSQL下多列无重音、模糊匹配问题

需求说明

在基于PostgreSQL的Django项目里,要给Article模型的title和content字段做无重音、大小写不敏感的模糊查询,具体要求:

  • 输入ronálD、Ron这类带重音或大小写混乱的内容,能精准匹配到含Ronald的文章
  • 输入Reagen(拼写近似)也能匹配到含Ronald Reagan的文章
  • 必须同时支持title和content两列的查询

模型定义

class Article(models.Model):
    date = models.DateField()
    content = models.TextField()
    title = models.CharField(max_length=300)
    category = models.CharField(max_length=50)
    length = models.IntegerField(default=4, null=True)
    thumbnail = models.TextField(null=True, blank=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    img_location = ArrayField(models.TextField(), blank=True, default=list, null=True)

当前问题与现有代码

目前已经实现了无重音、大小写不敏感的子串匹配,但有两个明显缺陷:

  1. 不支持近似拼写匹配——比如输入Reagen,匹配不到Ronald Reagan
  2. 只针对content单字段查询,没覆盖title字段

现有代码:

from django.db.models import Func, Value, F, Q


class Unaccent(Func):
    function = 'unaccent'

class Lower(Func):
    function = 'lower'
        
def normalize_field(field_name, is_array=False):
    if is_array:
        return Unaccent(Lower(F(field_name), function='unnest'))
    return Lower(Unaccent(F(field_name)))

normalized_term = Lower(Unaccent(Value(input_term)))

articles = list(Article.objects.annotate(
                normalized_content=normalize_field('content'),
            ).filter(
                Q(normalized_content__icontains=normalized_term) 
            ).order_by("-id").values()[:20]) 

解决方案

1. 先启用PostgreSQL必要扩展

要实现近似匹配和去重音,得先在数据库里开启两个扩展:

CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

2. 改造查询代码

直接替换原有逻辑,同时处理多列查询和近似匹配:

from django.db.models import Func, Value, F, Q
from django.contrib.postgres.search import TrigramSimilarity

class Unaccent(Func):
    function = 'unaccent'

def normalize_text(value):
    # 一步搞定去重音+转小写,统一文本格式
    return Unaccent(Func(value, function='lower'))

def search_articles(input_term):
    normalized_term = normalize_text(Value(input_term))
    
    # 给title和content做归一化注解,同时计算 trigram 相似度
    articles = Article.objects.annotate(
        normalized_title=normalize_text(F('title')),
        normalized_content=normalize_text(F('content')),
        # 计算和title的相似度
        title_similarity=TrigramSimilarity('normalized_title', normalized_term),
        # 计算和content的相似度
        content_similarity=TrigramSimilarity('normalized_content', normalized_term),
        # 总相似度取两者之和,排序用
        total_similarity=F('title_similarity') + F('content_similarity')
    ).filter(
        # 筛选相似度高于阈值(0.3可根据需求调整),同时保留精确子串匹配
        Q(total_similarity__gt=0.3) | 
        Q(normalized_title__icontains=normalized_term) | 
        Q(normalized_content__icontains=normalized_term)
    ).order_by('-total_similarity', '-id')[:20].values()
    
    return list(articles)

3. 代码说明

  • normalize_text:统一处理文本的去重音和转小写,避免大小写、重音带来的匹配误差
  • TrigramSimilarity:基于三元组算法实现近似拼写匹配,完美解决Reagen匹配Reagan的场景
  • 多列处理:同时给title和content做注解,过滤时覆盖两个字段
  • 排序逻辑:优先按相似度排序,相似度相同则按文章ID倒序,确保结果符合预期

4. 性能优化

如果数据量较大,建议给title和content创建专用索引,提升查询速度:

-- 给title创建带去重音的trigram索引
CREATE INDEX idx_article_title_unaccent_trgm ON article USING GIST (unaccent(lower(title)) gist_trgm_ops);
-- 给content创建带去重音的trigram索引
CREATE INDEX idx_article_content_unaccent_trgm ON article USING GIST (unaccent(lower(content)) gist_trgm_ops);

内容的提问来源于stack exchange,提问作者dreamer_deceiver

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 00:49:52