Django中如何对JSONField数组的对象属性进行模糊过滤?
问题描述
我在MySQL数据库中有如下结构的记录:
{ "id": 44207, "actors": [ { "id": "9c88bd9c-f41b-59fa-bfb6-427b1755ea64", "name": "APT41", "scope": "confirmed" }, { "id": "6f82bd9c-f31b-59fa-bf26-427b1355ea64", "name": "APT67", "scope": "confirmed" } ] }, { "id": 44208, "actors": [ { "id": "427b1355ea64-bfb6-59fa-bfb6-427b1755ea64", "name": "APT21", "scope": "confirmed" }, { "id": "9c88bd9c-f31b-59fa-bf26-427b1355ea64", "name": "APT22", "scope": "confirmed" } ] }, ...
其中actors是JSONField类型。我需要过滤出所有actors数组中任意对象的name字段包含'67'的记录。
我尝试过以下方法,但均存在局限:
- 使用
queryset.filter(actors__contains=[{"name":"APT67"}]):仅支持精确匹配actor.name的值,无法实现模糊匹配; - 使用
queryset.filter(actors__0__name__icontains='67'):仅能匹配数组第一个元素符合条件的记录; - 借助
models.Q构建多个OR条件:
search_query = models.Q(actors__0__name__icontains='67') | models.Q(actors__1__name__icontains='67') | models.Q(actors__2__name__icontains='67') | models.Q(actors__3__name__icontains='67') queryset.filter(search_query)
这种方式代码冗余,且仅能覆盖有限数量的数组元素。
请问是否存在更规范、通用的解决方法?
通用解决方法
方法1:利用MySQL原生JSON_SEARCH函数(推荐)
MySQL的JSON_SEARCH函数支持在JSON结构中进行模糊搜索,能直接定位数组内任意元素的指定字段是否匹配模糊条件。通过extra()方法嵌入原生SQL条件即可实现:
queryset = YourModel.objects.extra( where=["JSON_SEARCH(actors, 'one', '%67%', NULL, '$[*].name') IS NOT NULL"] )
参数说明:
'one':找到第一个匹配项即停止搜索,提升查询性能;'%67%':模糊匹配模式,对应SQL的LIKE语法;'$[*].name':指定搜索路径为actors数组中所有元素的name字段;- 若需不区分大小写匹配,可结合
LOWER()函数:
queryset = YourModel.objects.extra( where=["JSON_SEARCH(LOWER(actors), 'one', '%67%', NULL, '$[*].name') IS NOT NULL"] )
方法2:用Django Func表达式封装JSON函数
为了贴合Django ORM的写法,可直接使用Func类封装JSON_SEARCH,避免直接编写原生SQL字符串:
from django.db.models import Func queryset = YourModel.objects.filter( Func( 'actors', 'one', '%67%', None, '$[*].name', function='JSON_SEARCH' ).isnull(False) )
这种方式既保留了ORM的可读性,又能利用MySQL的JSON函数能力。
方法3:JSON_TABLE展开数组(MySQL 8.0+)
如果使用MySQL 8.0及以上版本,JSON_TABLE可以将JSON数组转换为关系型临时表,通过关联查询实现筛选,适合更复杂的数组元素分析场景:
queryset = YourModel.objects.raw(""" SELECT DISTINCT t.* FROM your_model_table t JOIN JSON_TABLE( t.actors, '$[*]' COLUMNS( name VARCHAR(255) PATH '$.name' ) ) jt WHERE jt.name LIKE '%67%' """)
注意替换your_model_table为你的实际数据库表名,DISTINCT用于避免因数组多个匹配元素导致的重复记录。
内容的提问来源于stack exchange,提问作者farefray
相关产品推荐
相关产品推荐

