在 Postgresql 的 WHERE 中使用 JSON 数组查询
Query with JSON array in WHERE in Postgresql
我有一个包含 payment_info JSONB 列的用户模型,其中包含以下 json 示例:
{
"customer_id": "cst_K5gCsCkKAU",
"subscriptions": [
{
"status": "active",
"external_id": "sub_3Q9Q4bP2zW"
}
]
}
我是一个有 JSON 查询的新手,但针对似乎有效的 Postgres (PG) 数据库创建了以下内容,即:我搜索具有特定 external_id 的所有用户价值:
SELECT payment_info->'subscriptions' as Subscriptions
FROM public."user"
, jsonb_array_elements(payment_info->'subscriptions') as subs
where (subs->>'external_id')::text = 'sub_3Q9Q4bP2zW'
如何在 SQLAlchemy 中执行相同的操作?我尝试了几种我在网上找到的想法 (SO),但它不起作用。我试过了:
-
query = misc.setup_query(db_session, User).filter(
User.payment_info.comparator.contains(
('subscriptions', 'external_id') == payment_subscription_id))
这会导致以下错误:
sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) operator does not exist: jsonb @> boolean
LINE 3: WHERE "user".payment_info @> false
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
json_contains
函数:
from sqlalchemy import func
query = misc.setup_query(db_session, User).filter(
func.json_contains(User.payment_info,
payment_subscription_id,
['subscriptions', 'external_id']))
这导致:
LINE 3: WHERE json_contains("user".payment_info, 'sub_QxyMEmU', ARRA...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
密钥路径:
query = misc.setup_query(db_session, User).filter(
User.payment_info['subscriptions', 'external_id'].astext == payment_subscription_id)
结果为空,查询如下:
SELECT *
FROM "user"
WHERE ("user".payment_info #>> %(payment_info_1)s) = %(param_1)s
我做错了什么,我该如何让它发挥作用?顺便说一句:我需要在 external_id
上添加索引吗? (尚未出现)
您几乎可以使用 aliased function expression:
来实现您原来的方法
misc.setup_query(db_session, User).\
select_from(
User,
func.jsonb_array_elements(User.payment_info['subscriptions']).
alias('subs')).\
filter(column('subs', type_=JSONB)['external_id'].astext == 'sub_3Q9Q4bP2zW')
编译为
SELECT "user".id AS user_id, "user".payment_info AS user_payment_info
FROM "user", jsonb_array_elements("user".payment_info -> %(payment_info_1)s) AS subs
WHERE (subs ->> %(subs_1)s) = %(param_1)s
另一方面,您可以使用 containment operator:
misc.setup_query(db_session, User).\
filter(User.payment_info['subscriptions'].contains(
[{'external_id': 'sub_3Q9Q4bP2zW'}]))
请注意,最外面的列表是必需的,因为它是要检查的“路径”的一部分。使用相同的逻辑,您可以省略提取数组:
misc.setup_query(db_session, User).\
filter(User.payment_info.contains(
{'subscriptions': [{'external_id': 'sub_3Q9Q4bP2zW'}]}))
上述 @>
使用方法可使用 GIN index 进行索引。第一个需要一个函数索引,因为它首先提取数组:
CREATE INDEX user_payment_info_subscriptions_idx ON "user"
USING GIN ((payment_info -> 'subscriptions'));
第二个需要索引整个 payment_info
jsonb 列。创建 GIN 索引可以在 SQLAlchemy 模型定义中完成 Postgresql-specific index options:
class User(Base):
...
Index('user_payment_info_subscriptions_idx',
User.payment_info['subscriptions'],
postgresql_using='gin')
至于为什么各种尝试都不成功:
您不应该直接访问比较器。它提供了类型的操作符。此外,您还传递 contains()
表达式
的结果
('subscriptions', 'external_id') == payment_subscription_id
最有可能是错误的(取决于 payment_subscription_id
是什么)。也就是说在Python.
中求值
There is no json_contains()
function 在 Postgresql 中(不同于 MySQL)。使用 @>
运算符,或 SQL/JSON 路径函数,例如 jsonb_path_exists()
.
你走错路了。 User.payment_info['subscriptions', 'external_id'].astext
会匹配 {"subscriptions": {"external_id": "foo"}}
之类的内容,但在您的数据中 subscriptions
引用了一个数组。
我有一个包含 payment_info JSONB 列的用户模型,其中包含以下 json 示例:
{
"customer_id": "cst_K5gCsCkKAU",
"subscriptions": [
{
"status": "active",
"external_id": "sub_3Q9Q4bP2zW"
}
]
}
我是一个有 JSON 查询的新手,但针对似乎有效的 Postgres (PG) 数据库创建了以下内容,即:我搜索具有特定 external_id 的所有用户价值:
SELECT payment_info->'subscriptions' as Subscriptions
FROM public."user"
, jsonb_array_elements(payment_info->'subscriptions') as subs
where (subs->>'external_id')::text = 'sub_3Q9Q4bP2zW'
如何在 SQLAlchemy 中执行相同的操作?我尝试了几种我在网上找到的想法 (SO),但它不起作用。我试过了:
-
query = misc.setup_query(db_session, User).filter( User.payment_info.comparator.contains( ('subscriptions', 'external_id') == payment_subscription_id))
这会导致以下错误:
sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) operator does not exist: jsonb @> boolean LINE 3: WHERE "user".payment_info @> false ^ HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
json_contains
函数:from sqlalchemy import func query = misc.setup_query(db_session, User).filter( func.json_contains(User.payment_info, payment_subscription_id, ['subscriptions', 'external_id']))
这导致:
LINE 3: WHERE json_contains("user".payment_info, 'sub_QxyMEmU', ARRA... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts.
密钥路径:
query = misc.setup_query(db_session, User).filter( User.payment_info['subscriptions', 'external_id'].astext == payment_subscription_id)
结果为空,查询如下:
SELECT * FROM "user" WHERE ("user".payment_info #>> %(payment_info_1)s) = %(param_1)s
我做错了什么,我该如何让它发挥作用?顺便说一句:我需要在 external_id
上添加索引吗? (尚未出现)
您几乎可以使用 aliased function expression:
来实现您原来的方法misc.setup_query(db_session, User).\
select_from(
User,
func.jsonb_array_elements(User.payment_info['subscriptions']).
alias('subs')).\
filter(column('subs', type_=JSONB)['external_id'].astext == 'sub_3Q9Q4bP2zW')
编译为
SELECT "user".id AS user_id, "user".payment_info AS user_payment_info
FROM "user", jsonb_array_elements("user".payment_info -> %(payment_info_1)s) AS subs
WHERE (subs ->> %(subs_1)s) = %(param_1)s
另一方面,您可以使用 containment operator:
misc.setup_query(db_session, User).\
filter(User.payment_info['subscriptions'].contains(
[{'external_id': 'sub_3Q9Q4bP2zW'}]))
请注意,最外面的列表是必需的,因为它是要检查的“路径”的一部分。使用相同的逻辑,您可以省略提取数组:
misc.setup_query(db_session, User).\
filter(User.payment_info.contains(
{'subscriptions': [{'external_id': 'sub_3Q9Q4bP2zW'}]}))
上述 @>
使用方法可使用 GIN index 进行索引。第一个需要一个函数索引,因为它首先提取数组:
CREATE INDEX user_payment_info_subscriptions_idx ON "user"
USING GIN ((payment_info -> 'subscriptions'));
第二个需要索引整个 payment_info
jsonb 列。创建 GIN 索引可以在 SQLAlchemy 模型定义中完成 Postgresql-specific index options:
class User(Base):
...
Index('user_payment_info_subscriptions_idx',
User.payment_info['subscriptions'],
postgresql_using='gin')
至于为什么各种尝试都不成功:
您不应该直接访问比较器。它提供了类型的操作符。此外,您还传递
的结果contains()
表达式('subscriptions', 'external_id') == payment_subscription_id
最有可能是错误的(取决于
中求值payment_subscription_id
是什么)。也就是说在Python.There is no
json_contains()
function 在 Postgresql 中(不同于 MySQL)。使用@>
运算符,或 SQL/JSON 路径函数,例如jsonb_path_exists()
.你走错路了。
User.payment_info['subscriptions', 'external_id'].astext
会匹配{"subscriptions": {"external_id": "foo"}}
之类的内容,但在您的数据中subscriptions
引用了一个数组。