postgresql 从JsonB数组中删除数值项

nsc4cvqm  于 2023-04-11  发布在  PostgreSQL
关注(0)|答案(3)|浏览(341)

我有一个嵌套JSON数组的jsonb值,需要删除一个元素:

{"values": ["11", "22", "33"]}

jsonb_set(column_name, '{values}', ((column_name -> 'values') - '33')) -- WORKS!

我也有一个类似的jsonb值,带有numbers,而不是字符串:

{"values": [11, 22, 33]}

jsonb_set(column_name, '{values}', ((column_name -> 'values') - 33))  -- FAILS!

在这种情况下,33被用作数组的索引。
如何从JSON数组中删除数字项?

30byixjq

30byixjq1#

两个论断:
1.许多Postgres JSON函数和操作符都以键/值对中的为目标。JSON数组中的字符串"abc""33")被视为没有值的键。但数字(33123.45)数组元素被视为 * 值 *。
1.-运算符目前有三种变体。其中两种适用于这里。正如最近澄清的手册所描述的(currently /devel):
操作员
说明
实施例
:————————————————————-
jsonb-textjsonb
从JSON对象中删除键(及其值),或从JSON数组中删除匹配的字符串值。
'{"a": "b", "c": "d"}'::jsonb - 'a'{"c": "d"}
'["a", "b", "c", "b"]'::jsonb - 'b'["a", "c"]
...
jsonb-integerjsonb
删除具有指定索引的数组元素(负整数从末尾开始计数)。
如果JSON值不是数组,则抛出错误。
'["a", "b"]'::jsonb - 1["a"]
如果右操作数是一个数字字面值,Postgres operator type resolution将得到后面的变体。
不幸的是,由于Assert1,我们不能使用前一个变体。
所以我们必须使用一个
workaround
像:

SELECT jsonb_set(column_name
               , '{values}'
               , (SELECT jsonb_agg(val)
                  FROM   jsonb_array_elements(t.column_name -> 'values') x(val)
                  WHERE  val <> jsonb '33')
                 ) AS column_name
FROM   tbl t;
  • db〈〉fiddle here* -- with extended test case

不要将非嵌套元素转换为integer(就像另一个答案建议的那样)。

  • 数值可能不适合integer
  • JSON数组(与Postgres数组不同)可以容纳多种元素类型。因此,一些数组元素可能是numeric,但其他数组元素可能是string等。
  • 强制转换所有数组元素(左边)的开销更大。只强制转换要替换的值(右边)。

所以这适用于 any 类型,而不仅仅是integer(JSON numeric)。例如:

'{"values": ["abc", "22", 33]}')
r6l8ljro

r6l8ljro2#

不幸的是,Postgres json运算符-只支持字符串值,如explained in the documentation
操作数:-
右操作数类型:text
产品描述:从左操作数中删除键/值对或字符串元素。键/值对根据其键值进行匹配。
另一方面,如果你传递一个整数值作为右操作数,Postgres会认为它是需要删除的数组元素的 index
另一种选择是使用jsonb_array_elements()和横向连接取消嵌套数组,过滤掉不需要的值,然后重新聚合:

select jsonb_set(column_name, '{values}', new_values) new_column_name
from mytable t
left join lateral (
    select jsonb_agg(val) new_values
    from jsonb_array_elements(t.column_name -> 'values') x(val)
    where val::int <> 33
) x on 1 = 1

Demo on DB Fiddle

with mytable as (select '{"values": [11, 22, 33]}'::jsonb column_name)
select jsonb_set(column_name, '{values}', new_values) new_column_name
from mytable t
left join lateral (
    select jsonb_agg(val) new_values
    from jsonb_array_elements(t.column_name -> 'values') x(val)
    where val::int <> 33
) x on 1 = 1
| new_column_name      |
| :------------------- |
| {"values": [11, 22]} |
wgx48brx

wgx48brx3#

PostgreSQL 12+

从这个版本开始,它具有强大的功能,可以使用jsonpath语法与JSONB一起工作。有一个很棒的article由该功能的作者编写,它比官方文档更好地描述了所有用例。

jsonb_path_query_array

获取除33以外的所有数组项:

SELECT jsonb_path_query_array(
        '{"values": [11, 22, 33]}'::jsonb,
        '$.values[*] ? (@ <> 33)'
)
-- [11, 22]

通过jsonb_set的组合,它给出了我们想要的:

jsonb_set(column_name, '{values}', jsonb_path_query_array(column_name, '$.values[*] ? (@ <> 33)'))

相关问题