从 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
  );

由于每一行(数据列)可能相当大,我试图从 itemsgroups 数组中过滤掉匹配的项目对象和组对象。

我目前的查询是这样的

SELECT * FROM campaign
WHERE 
(data -> 'items' @> '["productId": "123"]') OR
(data -> 'groups' @> '["groupId": "B"]')

返回包含匹配组或匹配项的行。但是,根据行的不同,data 列可能是一个相当大的 JSON 对象(items 中可能有数百个对象,groups 中可能有数十个对象,为了简洁起见,在此示例中我省略了几个键/属性) 这会影响查询性能(我在 itemsgroups 数组上添加了 GIN 索引,因此缺少索引并不是它变慢的原因)。

如何过滤掉 itemsgroups 数组以仅包含匹配的元素?

鉴于此匹配行


  "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 列中的多个数组中过滤掉对象的主要内容,如果未能解决你的问题,请参考以下文章

通过 jsonb 列中的给定键/值对过滤行

在 PostgreSQL 中的 jsonb 列中搜索 json 数组,其中数据为 json 数组

检测 jsonb 属性是数组还是对象

从对象 JSONB 中的数组中删除元素

如何从 PostgreSQL 中的 JSONB 数组中获取特定对象的值?

如何过滤 PySpark 中数组列中的值?