从 JSONB 列中的多个数组中过滤掉对象
Posted
技术标签:
【中文标题】从 JSONB 列中的多个数组中过滤掉对象【英文标题】:Filtering out objects from multiple arrays in a JSONB column 【发布时间】:2022-01-19 23:58:56 【问题描述】:我有一个 JSON 结构,其中两个数组保存在 JSONB 列中。稍微简化一下看起来是这样的
"prop1": "abc",
"prop2": "xyz",
"items": [
"itemId": "123",
"price": "10.00"
,
"itemId": "124",
"price": "9.00"
,
"itemId": "125",
"price": "8.00"
],
"groups": [
"groupId": "A",
"discount": "20",
"discountId": "1"
,
"groupId": "B",
"discount": "30",
"discountId": "2"
,
"groupId": "B",
"discount": "20",
"discountId": "3"
,
"groupId": "C",
"discount": "40",
"discountId": "4"
]
架构:
CREATE TABLE campaign
(
id TEXT PRIMARY KEY,
data JSONB
);
由于每一行(数据列)可能相当大,我试图从 items
和 groups
数组中过滤掉匹配的项目对象和组对象。
我目前的查询是这样的
SELECT * FROM campaign
WHERE
(data -> 'items' @> '["productId": "123"]') OR
(data -> 'groups' @> '["groupId": "B"]')
返回包含匹配组或匹配项的行。但是,根据行的不同,data
列可能是一个相当大的 JSON 对象(items
中可能有数百个对象,groups
中可能有数十个对象,为了简洁起见,在此示例中我省略了几个键/属性) 这会影响查询性能(我在 items
和 groups
数组上添加了 GIN 索引,因此缺少索引并不是它变慢的原因)。
如何过滤掉 items
和 groups
数组以仅包含匹配的元素?
鉴于此匹配行
"prop1": "abc",
"prop2": "xyz",
"items": [
"itemId": "123",
"price": "10.00"
,
"itemId": "124",
"price": "9.00"
,
"itemId": "125",
"price": "8.00"
],
"groups": [
"groupId": "A",
"discount": "20",
"discountId": "1"
,
"groupId": "B",
"discount": "30",
"discountId": "2"
,
"groupId": "B",
"discount": "20",
"discountId": "3"
,
"groupId": "C",
"discount": "40",
"discountId": "4"
]
我希望结果是这样的(匹配的项目/组可以在与 data
列的其余部分不同的列中 - 不必在具有两个数组的单个 JSON 对象中返回像这样,但如果不影响性能或导致非常麻烦的查询,我会更喜欢它):
"prop1": "abc",
"prop2": "xyz",
"items": [
"itemId": "123",
"price": "10.00"
],
"groups": [
"groupId": "B"
"discount": "20",
"discountId": "3"
]
到目前为止,我已经设法使用此查询展开并匹配 items
数组中的一个对象,这会从 data
列中删除“项目”数组并过滤掉匹配的 item
对象到单独的列,但我正在努力将其与 groups
数组中的匹配项一起加入。
SELECT data - 'items', o.obj
FROM campaign c
CROSS JOIN LATERAL jsonb_array_elements(c.data #> 'items') o(obj)
WHERE o.obj ->> 'productId' = '124'
如何在一个查询中过滤两个数组?
额外问题:对于groups
数组,我还想尽可能返回具有最低discount
值的对象。否则,结果将需要是匹配组对象的数组,而不是单个匹配的 group
。
相关问题:How to filter jsonb array elements和How to join jsonb array elements in Postgres?
【问题讨论】:
【参考方案1】:如果你的 postgres 版本是 12 或更高,你可以使用 jsonpath language 和 functions。下面的查询返回与给定条件匹配的项目和组子集的预期结果。然后,您可以在 sql 函数中调整此查询,使搜索条件成为输入参数。
SELECT jsonb_set(jsonb_set( data
, 'items'
, jsonb_path_query_array(data, '$.items[*] ? (@.itemId == "123" && @.price == "10.00")'))
, 'groups'
, jsonb_path_query_array(data, '$.groups[*] ? (@.groupId == "B" && @.discount == "20" && @.discountId == "3")'))
FROM (SELECT
'
"prop1": "abc",
"prop2": "xyz",
"items": [
"itemId": "123",
"price": "10.00"
,
"itemId": "124",
"price": "9.00"
,
"itemId": "125",
"price": "8.00"
],
"groups": [
"groupId": "A",
"discount": "20",
"discountId": "1"
,
"groupId": "B",
"discount": "30",
"discountId": "2"
,
"groupId": "B",
"discount": "20",
"discountId": "3"
,
"groupId": "C",
"discount": "40",
"discountId": "4"
]
' :: jsonb) AS d(data)
WHERE jsonb_path_exists(data, '$.items[*] ? (@.itemId == "123" && @.price == "10.00")')
AND jsonb_path_exists(data, '$.groups[*] ? (@.groupId == "B" && @.discount == "20" && @.discountId == "3")')
【讨论】:
效果很好,谢谢!我只需要调整我的 WHERE 子句以使用@>
运算符来确保我的 GIN 索引正在被使用。 IE。喜欢(data -> 'items' @> '["itemId": "123"]')
以上是关于从 JSONB 列中的多个数组中过滤掉对象的主要内容,如果未能解决你的问题,请参考以下文章
在 PostgreSQL 中的 jsonb 列中搜索 json 数组,其中数据为 json 数组