如何让Django将JSONField查询的__unaccent识别为PostgreSQL函数?
问题:Django JSONField中使用unaccent函数实现无重音模糊搜索
场景与问题
我有一个包含JSONField的模型:
class MyModel(models.Model): locale_names = models.JSONField()
该JSONField的结构为键是语言代码(如en、fr),值是对应翻译后的字符串。
我需要对JSON中指定语言的翻译值执行无重音的不区分大小写模糊搜索,尝试了以下查询:
MyModel.objects.filter(locale_names__en__unaccent__icontains="Test")
但未得到预期结果——因为Django将__unaccent解析为JSON路径的一部分,而非PostgreSQL的unaccent函数:
预期生成的SQL
-- 预期SQL查询:类似如下 SELECT "app_model"."*" ... FROM "app_model" WHERE UPPER(UNACCENT("app_model"."locale_names" ->>'en')::text) LIKE UPPER(UNACCENT('%Test%')) LIMIT 21
实际生成的SQL
-- 实际生成的SQL查询 SELECT "app_model"."*" ... FROM "app_model" WHERE UPPER(("app_model"."locale_names" #>> ARRAY['en','unaccent'])::text) LIKE UPPER('%Test%') LIMIT 21
补充信息:使用Django 3.2版本,普通CharField上执行__unaccent__icontains查询可正常工作。
解决方案
由于Django 3.2中JSONField的查找逻辑会优先解析__分隔的路径,需要先将JSON中的目标值提取为虚拟字段,再对该字段应用unaccent函数。
方案1:通过annotate提取字段后过滤
先通过annotate将JSON中指定语言的值提取为虚拟字段,再对该字段使用__unaccent__icontains:
from django.db.models import F, Func, Value from django.contrib.postgres.search import Unaccent # 提取locale_names中的en字段值为虚拟字段en_name queryset = MyModel.objects.annotate( en_name=Func(F('locale_names'), Value('en'), function='jsonb_extract_path_text') ).filter( en_name__unaccent__icontains="Test" )
此方法会生成符合预期的SQL,unaccent会被正确识别为PostgreSQL函数。
方案2:直接用Func构建查询条件
如果不想使用annotate,可以直接通过Func嵌套调用,将unaccent应用在JSON提取的值上:
from django.db.models import Func, F, Value from django.contrib.postgres.search import Unaccent queryset = MyModel.objects.filter( # 先提取JSON的en值,再应用unaccent函数 Func( Func(F('locale_names'), Value('en'), function='jsonb_extract_path_text'), function='unaccent' )__icontains=Unaccent('Test') )
原理说明
通过annotate或直接嵌套Func,我们先将JSON中的目标值转换为普通的文本表达式,之后Django处理__unaccent__icontains时,就会将其解析为PostgreSQL的unaccent函数调用,而非JSON路径的一部分。
内容的提问来源于stack exchange,提问作者foucdeg
相关产品推荐
相关产品推荐

