【问题标题】:Django filter JSONField when key is a number当键是数字时,Django过滤JSONField
【发布时间】:2022-01-26 16:21:35
【问题描述】:

node_status是JOSNField,如何过滤node_status有{'2': True}的queryset。

>>> instance.node_status
{'cat': '1', '2': True, 'dog': True}
>>> qs.filter(node_status__cat='1')
Yeah got result
>>> qs.filter(node_status__has_key='dog')
Yeah got result
>>> qs.filter(node_status__2=True)
<QuerySet []>

node_status__2=True 得到空查询集。

#models.py
from django.db import models
class Foo(models.Model):
    node_status = models.JSONField(null=True, blank=True)

instance = Foo.objects.create(node_status={'cat': '1', '2': True, 'dog': True})
qs = Foo.objects.all()
qs.filter(node_status__cat='1')
qs.filter(node_status__has_key='dog')
qs.filter(node_status__2=True)

环境:

MariaDB 10.2
Django  4.0.1

【问题讨论】:

  • 能否添加Model 示例代码以及如何插入{'cat': '1', '2': True, 'dog': True} 数据?当前的问题是可以理解的,但很难重现
  • 我添加了更多细节。

标签: django


【解决方案1】:

Django 自动将类似数字的值转换为数字和数组索引:

让我们比较原始的SQLs

print(Foo.objects.filter(node_status__has_key='dog').query)
print(Foo.objects.filter(node_status__has_key='2').query)
SELECT `django4_foo`.`id`, `django4_foo`.`node_status` FROM `django4_foo` WHERE JSON_CONTAINS_PATH(`django4_foo`.`node_status`, 'one', $."dog")
SELECT `django4_foo`.`id`, `django4_foo`.`node_status` FROM `django4_foo` WHERE JSON_CONTAINS_PATH(`django4_foo`.`node_status`, 'one', $[2])

如您所见,2 被转换为$[2],但dog 被转换为$."dog"

一种可能的解决方案是获取原始 SQL,手动重新格式化并使用原始 SQL 初始化新的查询集:

raw_sql = str(Foo.objects.filter(node_status__has_key='2').query).replace(f'$[2]', f'\'$.2\'')

print(raw_sql)

rqs = Foo.objects.raw(raw_sql)

for o in rqs:
  print(o.node_status)
SELECT `django4_foo`.`id`, `django4_foo`.`node_status` FROM `django4_foo` WHERE JSON_CONTAINS_PATH(`django4_foo`.`node_status`, 'one', '$.2')
{'cat': '1', '2': True, 'dog': True}

但是,此 hack 不适用于 node_status__2=True 过滤器。

让我们检查这个查询是否有 dog 键:

print(Foo.objects.filter(node_status__dog=True).query)

输出是:

SELECT `django4_foo`.`id`, `django4_foo`.`node_status` FROM `django4_foo` WHERE JSON_UNQUOTE(JSON_EXTRACT(`django4_foo`.`node_status`, $."dog")) = JSON_EXTRACT(true, '$')

但实际工作的原始 SQL 是:

SELECT `django4_foo`.`id`, `django4_foo`.`node_status` FROM `django4_foo` WHERE JSON_UNQUOTE(JSON_EXTRACT(`django4_foo`.`node_status`, "$.dog")) = 'True'

所以filterraw 转换存在很多问题。 (Django 社区也不建议使用 str(query) 生成原始 SQL 查询

Django 的定位就像适用于所有类型螺丝的万能螺丝刀。内部冲突和过于复杂的“通用且多合一”逻辑是 Django 的常见问题。

是的,可以覆盖query 类和change key transformation logic,但它很难阅读和理解,非常复杂,并且自定义逻辑可能会出现另一个问题(因为 Django 不希望逻辑被更改)。

所以更简单的方法是:

  • 只需使用Foo.objects.raw(sql_query),其中sql_query 是手动预定义的字符串
  • 使用cursor.execute(sql_query, \[params\])sql_query 预定义与①相同
  • 获取 Foo.objects.all() 并使用 python 逻辑对其进行过滤 ([e for e in Foo.objects.all() if '2' in e.node_status])

其他可能的方式有:

※ 我的选择;)

【讨论】:

    猜你喜欢
    • 2016-07-23
    • 2016-03-25
    • 2021-04-03
    • 1970-01-01
    • 2018-10-02
    • 2023-03-28
    • 2020-08-13
    • 2020-10-08
    • 2018-04-09
    相关资源
    最近更新 更多