簡體   English   中英

從 PostgreSQL JSON 字段中選擇數據 SQLAlchemy / Flask SQLAlchemy

[英]Selecting data from PostgreSQL JSON field with SQLAlchemy / Flask SQLAlchemy

我在數據庫中的 JSON 是這樣的:

{
    "ingredients": [
        "3 tablespoons almond butter",
        ...
        "Lime wedges"
    ],
    "instructions": [
        "In a bowl" 
    ]
    "tags": [
        "gluten-free",
        "grain bowls",
        "Thai"
     ]
}

我的 app.py 文件:

class Recipe(db.Model):
    __tablename__ = "recipe"
    ....
    recipe = db.Column(db.JSON)

對於初學者,我正在嘗試 select 所有帶有“Thai”標簽的記錄。

Recipe.query.filter(Recipe.recipe['tags'].contains(["Thai"])).all()

告訴我我可能需要顯式類型轉換,我已經嘗試過 astext 和 cast.. 似乎沒有任何效果。 不知道我做錯了什么。

這是堆棧跟蹤的結尾:

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%"]'}]

我嘗試了:

Recipe.query.filter(Recipe.recipe['tags'].contains(cast(["%Thai%"], JSON)))

有效的是

db.engine.execute(text("select * from recipe where recipe->> 'tags' like '%Thai%'"))

我可以忍受,但我頑固地希望它通過 ORM 發生。

我唯一的線索是在堆棧跟蹤中我看到它生成了一個 sql 語句 -> 不是 ->>... 但我不知道如何獲得 ->>

然后我發現了這個:

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%"))

並且沒有錯誤,但是結果是空的......我想我現在有點接近了@rmn

您可以通過將表達式的左側轉換為文本類型來避免錯誤:

# select cast('{"a": ["spam", "ham"]}'::json->'a' as text) like '%spam%' as "Exists?";
 Exists? 
═════════
 t

但這也將匹配 substrings ,這可能是不可取的:

# select cast('{"a": ["spam", "ham"]}'::json->'a' as text) like '%am%' as "Exists?";
 Exists? 
═════════
 t

更好的選擇是轉換為 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

在 Python 方面,這看起來像

from sqlalchemy import cast
from sqlalchemy.dialects.postgresql import JSONB

result = Recipe.query.filter(
    cast(Recipe.recipe['tags'], JSONB).contains(["Thai"])
).all()

將列類型更改為JSONB將消除強制轉換的需要(但需要遷移):

class Recipe(db.Model)
    ...
    recipe = sb.Column(JSONB)

result = Recipe.filter(Recipe.recipe['tags'].contains(['Thai'])).all()

您可以在源代碼中查看 JSON(B) 比較方法 map 到 Postgresql 的功能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM