web-dev-qa-db-fra.com

filtre sqlalchemy par champ json

J'ai un modèle avec json column. Exemple de modèle et de données:

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()

Maintenant, j'essaie de trouver des enregistrements où id == 1:

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

Et je reçois la prochaine erreur:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) n'existe pas: json = entier LIGNE 3: OERE (exemple.json_field -> 'id') = 1

La raison. Regardez la requête générée:

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

Mais dans mon cas, la requête correcte devrait être comme ceci:

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

Comment faire?

J'ai essayé d'utiliser cast , mais sans succès:

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

L'erreur:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) ne peut pas convertir le type json en entier LIGNE 3: OERE CAST ((exemple.json_field -> 'id') COMME INTEGER) = 1

Comme vous pouvez le voir where clause toujours incorrecte. Je dois également utiliser la plage (>, <= etc.) conditions. Merci pour l'aide.

9
Danila Ganchar

Objet SQLAlchemy de Flask-SQLAlchemy - communément appelé db - donne accès aux fonctions etc. de sqlalchemy et sqlalchemy.orm, Etc. db.JSON Est le type générique JSON qui ne fournit pas les opérateurs spécifiques à Postgresql. Vous devriez plutôt utiliser 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)

Avec le type approprié en place, vous devez d'abord convertir explicitement le JSON en texte puis convertir en un entier:

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

Cela produit le prédicat souhaité

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

La même chose s'applique à tous les types qui ne peuvent pas être directement convertis à partir de json. SQLAlchemy offrait un raccourci pour la combinaison de astext et cast(), mais il a été supprimé dans les versions 1.1 et supérieures:

Modifié dans la version 1.1: L'opérateur ColumnElement.cast() sur les objets JSON nécessite désormais que le modificateur JSON.Comparator.astext Soit appelé explicitement, si le transtypage fonctionne uniquement à partir d'une chaîne textuelle.

12
Ilja Everilä

Vous pouvez également utiliser SQL brut dans le filtre

from sqlalchemy import text

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

si vous utilisez uniquement filter(json_obj["key"] ... ) il se convertira en sql comme model_name.json_obj -> 'key', qui est toujours un objet json,

si vous utilisez filter(json_obj["key"].astext ...), le sql sera model_name.json_obj ->> 'key', le résultat est un objet chaîne.

0
pumpkindle