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

SQLAlchemy如何实现JSONB类型字典字段的属性值过滤查询

问题描述

表结构如下:

+-------------------------------------------------------------+
| id | res_id    | path                   | overrides         |
|-------------------------------------------------------------|
| 1  | res_1     | res_1                  | {"enabled": True} |
| 2  | res_1.1   | res_1.res_1.1          | {"enabled": False}|
| 3  | res_1.2   | res_1.res_1.2          | {"enabled": False}|
| 4  | res_1.1.1 | res_1.res_1.1.res_1.1.1| {"enabled": False}|
+-------------------------------------------------------------+
  • 表中overrides字段为JSONB类型,用于标记继承关系中断点:正常查询res_1.1.1时,按照继承逻辑会同时返回路径上关联的id=1、id=2和自身id=4的资源;但id=1的记录overrides.enabled值为True,代表该节点不会向下传递继承关系,因此该场景预期仅返回id=2、id=4的记录。
  • 尝试实现「过滤掉overrides字段中enabled值为True的记录」时,采用.filter(MyModel.overrides["enabled"] is not True)写法未生效;尝试调用字典原生的get()、values()、keys()方法时,抛出AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with MyModel.overrides has an attribute 'get'错误,无法实现过滤逻辑。
错误原因
  • is not是Python原生的身份判断运算符,在传入filter()方法前就会被Python解释器直接求值为布尔值,无法被SQLAlchemy解析为数据库层面的查询条件,自然不会生效。
  • 在模型类层面访问MyModel.overrides拿到的是ORM字段描述符(InstrumentedAttribute对象),不是从数据库加载完成后反序列化得到的Python字典,因此无法直接调用字典的get()、keys()等原生方法——这些方法只能在记录被查询加载到内存后,在实例对象的字段值上调用,无法用于生成数据库层面的过滤SQL。
正确实现方式

SQLAlchemy针对PostgreSQL的JSONB类型提供了专属的比较操作符,不需要手动加载所有记录到内存过滤,直接在数据库层即可完成条件判断:

  1. 基础过滤写法(适用于SQLAlchemy 1.3+所有版本)
from sqlalchemy import Boolean, true

# 过滤掉overrides.enabled为True的记录,兼容键不存在的场景
query = MyModel.query.filter(
    # 当enabled键不存在时,取值为SQL null,与true()比较结果为不相等,会被保留
    MyModel.overrides["enabled"].astext.cast(Boolean) != true()
)
  1. 更简洁的写法(适用于SQLAlchemy 2.0+,或1.4版本开启2.0兼容模式)
    SQLAlchemy 2.0为JSON类型提供了直接的类型转换方法,不需要手动调用cast:
from sqlalchemy import select

valid_records = db.session.execute(
    select(MyModel).where(MyModel.overrides["enabled"].as_boolean() != True)
).scalars().all()
  1. 结合继承路径查询场景的完整示例
    查询res_1.1.1关联的有效继承节点时,可以直接将过滤条件和路径前缀匹配条件组合:
target_path = "res_1.res_1.1.res_1.1.1"

valid_nodes = MyModel.query.filter(
    # 匹配所有当前路径的前缀节点(含自身)
    target_path.like(f"{MyModel.path}%"),
    # 排除开启了继承中断的节点
    MyModel.overrides["enabled"].astext.cast(Boolean) != True
).all()

执行上述查询后,id=1的记录会因为overrides.enabled=True被过滤,最终返回id=2、id=4的记录,符合预期。

注意:如果表中存在overrides字段本身为NULL、或者overrides里没有enabled键的记录,上述写法会自动保留这些记录——因为JSON路径取值不存在时会返回SQL NULL,和True比较的结果为不相等,刚好符合「仅当明确设置enabled=True时才中断继承」的业务逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 00:39:02