[英]Selecting data from PostgreSQL JSON field with SQLAlchemy / Flask SQLAlchemy
My JSON in the db is like this:我在数据库中的 JSON 是这样的:
{
"ingredients": [
"3 tablespoons almond butter",
...
"Lime wedges"
],
"instructions": [
"In a bowl"
]
"tags": [
"gluten-free",
"grain bowls",
"Thai"
]
}
My app.py file:我的 app.py 文件:
class Recipe(db.Model):
__tablename__ = "recipe"
....
recipe = db.Column(db.JSON)
I'm trying, for starters, to select all records with the "Thai" tag.对于初学者,我正在尝试 select 所有带有“Thai”标签的记录。
Recipe.query.filter(Recipe.recipe['tags'].contains(["Thai"])).all()
tells me I might need explicit type casts, I've tried astext and cast.. nothing seems to be working.告诉我我可能需要显式类型转换,我已经尝试过 astext 和 cast.. 似乎没有任何效果。 Not sure what I'm doing wrong.不知道我做错了什么。
here's the end of the stack trace:这是堆栈跟踪的结尾:
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedFunction) operator does not exist: json ~~ text
LINE 3: WHERE ((recipe.recipe -> 'tags') LIKE '%' || CAST('["%Thai%"...
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
[SQL: SELECT recipe.id AS recipe_id, recipe.url_id AS recipe_url_id, recipe.author AS recipe_author, recipe.description AS recipe_description, recipe.recipe AS recipe_recipe
FROM recipe
WHERE ((recipe.recipe -> %(recipe_1)s) LIKE '%%' || CAST(%(param_1)s AS JSON) || '%%')]
[parameters: {'recipe_1': 'tags', 'param_1': '["%Thai%"]'}]
With that I tried:我尝试了:
Recipe.query.filter(Recipe.recipe['tags'].contains(cast(["%Thai%"], JSON)))
What has worked is有效的是
db.engine.execute(text("select * from recipe where recipe->> 'tags' like '%Thai%'"))
I can live with that but am stubbornly wanting it to happen thru the ORM.我可以忍受,但我顽固地希望它通过 ORM 发生。
The only clue I have is that in the stack trace I'm seeing it generates a sql statement with -> not ->>... but I can't figure how to get the ->>我唯一的线索是在堆栈跟踪中我看到它生成了一个 sql 语句 -> 不是 ->>... 但我不知道如何获得 ->>
Then I found this:然后我发现了这个:
tags_subquery = db.session.query(func.json_array_elements(Recipe.recipe['tags']).label('tags')).subquery()
query = db.session.query(Recipe, tags_subquery.c.tags).filter(tags_subquery.c.tags.op('->>')('tags').like("%Thai%"))
And there's no error, but an empty result... I think I'm somewhat close now thanks to @rmn并且没有错误,但是结果是空的......我想我现在有点接近了@rmn
You can avoid the error by casting the left hand side of the expression to a text type:您可以通过将表达式的左侧转换为文本类型来避免错误:
# select cast('{"a": ["spam", "ham"]}'::json->'a' as text) like '%spam%' as "Exists?";
Exists?
═════════
t
but this will also match substrings , which may not be desirable:但这也将匹配 substrings ,这可能是不可取的:
# select cast('{"a": ["spam", "ham"]}'::json->'a' as text) like '%am%' as "Exists?";
Exists?
═════════
t
A better option would be to cast to jsonb:更好的选择是转换为 jsonb:
# select cast('{"a": ["spam", "ham"]}'::json->'a' as jsonb) @> '["spam"]' as "Exists?";
Exists?
═════════
t
# select cast('{"a": ["spam", "ham"]}'::json->'a' as jsonb) @> '["am"]' as "Exists?";
Exists?
═════════
f
On the Python side, this would look like在 Python 方面,这看起来像
from sqlalchemy import cast
from sqlalchemy.dialects.postgresql import JSONB
result = Recipe.query.filter(
cast(Recipe.recipe['tags'], JSONB).contains(["Thai"])
).all()
Changing the column type to JSONB would remove the need for casting (but would require a migration):将列类型更改为JSONB将消除强制转换的需要(但需要迁移):
class Recipe(db.Model)
...
recipe = sb.Column(JSONB)
result = Recipe.filter(Recipe.recipe['tags'].contains(['Thai'])).all()
You can see how JSON(B) comparison methods map to Postgresql functions in the source .您可以在源代码中查看 JSON(B) 比较方法 map 到 Postgresql 的功能。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.