【问题标题】:sqlalchemy filter by json fieldsqlalchemy 按 json 字段过滤
【发布时间】:2019-04-15 06:50:48
【问题描述】:

我有json column 的模型。模型和数据示例:

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://...'

db = SQLAlchemy()
db.init_app(app)
app.app_context().push()

class Example(db.Model):
    id = db.Column(db.Integer(), nullable=False, primary_key=True, )
    json_field = db.Column(db.JSON())

db.create_all()
db.session.add(Example(json_field={'id': None}))
db.session.add(Example(json_field={'id': 1}))
db.session.add(Example(json_field={'id': 50}))
db.session.add(Example(json_field={}))
db.session.commit()

现在我尝试查找id == 1:

query = db.session.query(Example).filter(Example.json_field['id'] == 1)
print(query.all())

我得到下一个错误:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) 运算符 不存在:json = integer LINE 3: WHERE (example.json_field -> 'id') = 1

原因。查看生成的查询:

SELECT example.id AS example_id, example.json_field AS example_json_field 
FROM example 
WHERE (example.json_field -> %(json_field_1)s) = %(param_1)s

但在我的情况下,正确的查询应该是这样的:

SELECT * FROM example WHERE CAST(json_field->>'id' AS INTEGER) = 1;

我该怎么做?

我尝试过使用cast,但没有成功:

print(
    db.session.query(Example).filter(
        cast(Example.json_field['id'], Integer) == 1
    ).all()
)

错误:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) 不能 将类型 json 转换为整数 LINE 3: WHERE CAST((example.json_field -> 'id') 作为整数) = 1

如您所见,where clause 仍然是错误的。我还需要使用范围(><= 等)条件。感谢您的帮助。

【问题讨论】:

    标签: python sqlalchemy flask-sqlalchemy


    【解决方案1】:

    Flask-SQLAlchemy 的 SQLAlchemy 对象 - 通常命名为 db - gives access 到来自 sqlalchemysqlalchemy.orm 的函数等,因此 db.JSON 是不提供 Postgresql 特定的 generic JSON type运营商。你应该改用sqlalchemy.dialects.postgresql.JSON:

    from sqlalchemy.dialects.postgresql import JSON
    
    class Example(db.Model):
        id = db.Column(db.Integer(), nullable=False, primary_key=True, )
        json_field = db.Column(JSON)
    

    使用适当的类型,您必须先将JSON to text 显式转换为整数:

    db.session.query(Example).\
        filter(Example.json_field['id'].astext.cast(Integer) == 1)
    

    这会产生所需的谓词

    CAST(json_field->>'id' AS INTEGER) = 1
    

    这同样适用于所有不能直接从json 转换的类型。 SQLAlchemy 曾经提供astextcast() 组合的快捷方式,但在1.1 及更高版本中已被删除:

    在 1.1 版中已更改:JSON 对象上的 ColumnElement.cast() 运算符现在需要显式调用 JSON.Comparator.astext 修饰符,如果强制转换仅适用于文本字符串。

    【讨论】:

      【解决方案2】:

      您也可以在过滤器中使用原始 sql

      from sqlalchemy import text
      
      db.session.query(Example).filter(text("CAST(json_field->>'id' AS INTEGER) = 1")
      

      【讨论】:

      • 不太安全,如果使用参数而不是 ORM 友好风格:-)
      【解决方案3】:

      如果你只使用filter(json_obj["key"] ... ) 它将转换为 sql 之类的 model_name.json_obj -> 'key',还是个json对象,

      如果你使用filter(json_obj["key"].astext ...),sql 将是 model_name.json_obj ->> 'key',结果是一个字符串对象。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-08-04
        • 1970-01-01
        • 1970-01-01
        • 2023-01-17
        • 1970-01-01
        • 1970-01-01
        • 2012-02-12
        • 2015-06-01
        相关资源
        最近更新 更多