【问题标题】:Delete element in a deeply nested array in jsonb column - Postgres删除 jsonb 列中深度嵌套数组中的元素 - Postgres
【发布时间】:2019-09-12 15:23:28
【问题描述】:

我有一个表my_table,其中包含一些数据的jsonb列,例如,在单行中,该列可以包含以下数据:

[
  {
    "x_id": "1",
    "type": "t1",
    "parts": [
       { "part_id": "1", price: 400 },
       { "part_id": "2", price: 500 },
       { "part_id": "3", price: 0 }
     ]
  },
  {
    "x_id": "2",
    "type": "t1",
    "parts": [
       { "part_id": "1", price: 1000 },
       { "part_id": "3", price: 60 }
     ]
  },
  {
    "x_id": "3",
    "type": "t2",
    "parts": [
       { "part_id": "1", price: 100 },
       { "part_id": "3", price: 780 },
       { "part_id": "2", price: 990 }
     ]
  }
]

在给定x_idpart_id 的情况下,我需要帮助查找如何从parts 数组中删除元素。

示例

鉴于x_id=2part_id=1,我需要将数据更新为:

[
  {
    "x_id": "1",
    "type": "t1",
    "parts": [
       { "part_id": "1", price: 400 },
       { "part_id": "2", price: 500 },
       { "part_id": "3", price: 0 }
     ]
  },
  {
    "x_id": "2",
    "type": "t1",
    "parts": [
       { "part_id": "3", price: 60 }
     ]
  },
  {
    "x_id": "3",
    "type": "t2",
    "parts": [
       { "part_id": "1", price: 100 },
       { "part_id": "3", price: 780 },
       { "part_id": "2", price: 990 }
     ]
  }
]

PS1:这些数据无法标准化,所以这不是一个可能的解决方案。

PS2:我正在运行 PostgreSQL 9.6

PS3:我检查了this questionthis question,但与其他问题相比,我的数据结构似乎过于复杂,因此我无法应用给定的答案。

Edit1:json 数据可能很大,尤其是parts 数组,它的元素可以从少到 0 到几千。

【问题讨论】:

  • 最好在 SQL 之外完成。
  • 我把它作为最后的手段,以防数据库中只有一个查询就无法做到这一点。
  • 你能改变JSON结构吗?数组本质上很难使用。如果您可以将其更改为 this 之类的内容,那会容易得多,因为您可以使用类似引用的路径访问所有内容。
  • 很遗憾,这是不可能的
  • 每个 json 元素的 x_id 是否唯一?

标签: sql json postgresql jsonb postgres-9.6


【解决方案1】:

这应该可以,只需要另一个唯一列(通常是主键)

创建测试表

create table test_tab(
id serial primary key,
j jsonb
);

insert into test_tab
(j)
values
('[
  {
    "x_id": "1",
    "type": "t1",
    "parts": [
       { "part_id": "1", "price": 400 },
       { "part_id": "2", "price": 500 },
       { "part_id": "3", "price": 0 }
     ]
  },
  {
    "x_id": "2",
    "type": "t1",
    "parts": [
       { "part_id": "1", "price": 1000 },
       { "part_id": "3", "price": 60 }
     ]
  },
  {
    "x_id": "3",
    "type": "t2",
    "parts": [
       { "part_id": "1", "price": 100 },
       { "part_id": "3", "price": 780 },
       { "part_id": "2", "price": 990 }
     ]
  }
]');

然后拆分json,过滤掉不需要的数据,重新创建json:

 select id, jsonb_agg( jsonb_build_object('x_id',xid, 'type',type, 'parts', case when inner_arr = '[null]'::jsonb  then parts_arr::jsonb else inner_arr  end) ) 
 from (
    select 
    id, 
     value->>'x_id' as xid, 
    jsonb_agg(inner_arr) as inner_arr,
    max(value->>'parts') as parts_arr,
    max(value->>'type') as type
    from (
        select * , 
        case when value->>'x_id'='2' then jsonb_array_elements(value->'parts')  else NULL end inner_arr 
        from test_tab
        join lateral jsonb_array_elements(j)
        on true
    ) t
    where
    inner_arr->>'part_id'  is distinct from '1'
    group by id, value->>'x_id' 
) t
group by id

【讨论】:

  • 我确实有一个主键,所以这不会是一个问题,我试过这个,它似乎工作,我给了一些不存在的 ID 值,它不会崩溃,所以非常感谢。您认为此解决方案是否有任何可能导致崩溃的边缘情况?
  • @ElSam - 如果您的 json 始终具有与您显示的问题相同的结构,我认为它永远不会崩溃
  • 这在非常大的数据上表现不佳,其中parts 数组超过 1000 个元素。我在这些数据上运行它,它永远挂起。
【解决方案2】:

我觉得你可以使用#-操作符(见functions-json),你只需要找到移除数组元素的路径:

select
    data #- p.path
from test as t
    cross join lateral (
        select array[(a.i-1)::text,'parts',(b.i-1)::text]
        from jsonb_array_elements(t.data) with ordinality as a(data,i),
            jsonb_array_elements(a.data->'parts') with ordinality as b(data,i)
        where
            a.data ->> 'x_id' = '2' and
            b.data ->> 'part_id' = '1'
    ) as p(path)

update test as t set
    data = data #- (
        select
            array[(a.i-1)::text,'parts',(b.i-1)::text]
        from jsonb_array_elements(t.data) with ordinality as a(data,i),
            jsonb_array_elements(a.data->'parts') with ordinality as b(data,i)
        where
            a.data ->> 'x_id' = '2' and
            b.data ->> 'part_id' = '1'
    )

db<>fiddle demo

update 好的,如果数据中不存在给定路径,则更新部分无法正常工作,这是合理的评论。我猜在这种情况下,您将在 where 子句中重复表达式:

update test as t set
    data = data #- (
        select
            array[(a.i-1)::text,'parts',(b.i-1)::text]
        from jsonb_array_elements(t.data) with ordinality as a(data,i),
            jsonb_array_elements(a.data->'parts') with ordinality as b(data,i)
        where
            a.data ->> 'x_id' = '2' and
            b.data ->> 'part_id' = '23222'
    )
where
    exists (
        select *
        from jsonb_array_elements(t.data) as a(data),
            jsonb_array_elements(a.data->'parts') as b(data)
        where
            a.data ->> 'x_id' = '2' and
            b.data ->> 'part_id' = '23222'
    )

db<>fiddle demo

或者你可以使用自加入:

update test as t2 set
    data = t.data #- p.path
from test as t
    cross join lateral (
        select array[(a.i-1)::text,'parts',(b.i-1)::text]
        from jsonb_array_elements(t.data) with ordinality as a(data,i),
            jsonb_array_elements(a.data->'parts') with ordinality as b(data,i)
        where
            a.data ->> 'x_id' = '2' and
            b.data ->> 'part_id' = '23232'
    ) as p(path)
where
    t.ctid = t2.ctid

db<>fiddle demo

【讨论】:

  • 如果给定的x_idpart_id 不存在,您的解决方案似乎将数据更新为空。
  • 感谢您的更新,但是,这在零件数组超过 1000 个元素的非常大的数据上表现不佳。我在这些数据上运行它,它永远挂起。
  • 第一个 sn-p,因为我提供了一个存在的路径,并希望在更新之前查看选择结果。
  • 我做了一个快速测试 (dbfiddle.uk/…) 但老实说,我不希望它在大量数据上表现得很好
猜你喜欢
  • 2015-07-12
  • 2021-02-28
  • 1970-01-01
  • 2017-03-28
  • 2021-08-21
  • 1970-01-01
  • 2017-08-19
  • 2013-11-22
  • 1970-01-01
相关资源
最近更新 更多