从JSON数组中移除一个对象

huangapple go评论65阅读模式
英文:

Remove an object from a JSON array

问题

从我的 PostgreSQL 12.8 数据库的表行中,我试图从数组中删除一个对象,但实际上是删除了整个数组中的对象。

settings 列包含以下对象数组:

[
    {
        "id": 100,
        "name": "testOne",
        "settings": "settingOne"
    },
    {
        "id": 101,
        "name": "testTwo",
        "settings": "settingTwo"
    }
]

我在 users 表中有三行,其中 settings 列的数据类型为 jsonb,其中包含对象数组。

我想要删除所有用户中 id 为 101 的对象。我尝试了以下查询:

update users
set settings = 
    jsonb_set(settings , '{settings}', (settings->'id') - (select distinct position-1 from users, jsonb_array_elements(settings) 
    with ordinality arr(elem, position) WHERE elem->>'id' = '101')::int)

通过执行上面的查询,它删除了设置中的所有内容。我应该如何修改上面的查询以达到以下结果?

[
    {
        "id": 100,
        "name": "testOne",
        "settings": "settingOne"
    }
]
英文:

From table rows in my PostgreSQL 12.8 database, I am trying to remove an object from an array, but instead it is deleting whole array of objects from the table.

settings column holds below array of objects:

    [
        {
            "id": 100,
            "name": "testOne",
            "settings": "settingOne"
        },
        {
            "id": 101,
            "name": testTwo,
            "settings": "settingTwo"
        },
]

I have three rows in the users table with the column settings of type jsonb that holds an array of objects.

I want to delete the object with id = 101 for all users. I tried the below query:

update users
set settings = 
    jsonb_set(settings , '{settings}', (settings->'id') - (select distinct position-1 from users, jsonb_array_elements(settings) 
    with ordinality arr(elem, position) WHERE elem->>'id' = '101')::int)

By executing the above query it is deleting everything from the settings. How can I modify the above query in order to achieve the below result?

   [
            {
                "id": 100,
                "name": "testOne"
                "settings": "settingOne"
            }
    ]

答案1

得分: 2

这应该可以:

UPDATE users u
SET    settings = (SELECT jsonb_agg(a.elem)
                   FROM   jsonb_array_elements(u.settings) AS a(elem)
                   WHERE  (a.elem ->> 'id' = '101') IS NOT TRUE)
WHERE  u.settings @> '[{"id":101}]'';

fiddle

添加的外部 WHERE 子句确保只更新实际发生变化的行。您可以有附加的筛选条件...

这将从数组中删除具有 "id": 101所有对象。(可能不止一个。)

要仅删除第一个匹配项:

UPDATE users u
SET    settings = u.settings
                - (SELECT a.ord::int-1
                   FROM   jsonb_array_elements(u.settings) WITH ORDINALITY a(elem, ord)
                   WHERE  a.elem ->> 'id' = '101'
                   LIMIT  1)
WHERE  u.settings @> '[{"id":101}]'';

fiddle

请注意,在相关子查询中不要重复表名,这将导致额外的开销 - 并需要额外的筛选条件以链接回更新的表。

-1 是因为Postgres的序号从1开始,而JSON数组元素从0开始。

关于 WITH ORDINALITY

英文:

This should do it:

UPDATE users u
SET    settings = (SELECT jsonb_agg(a.elem)
                   FROM   jsonb_array_elements(u.settings) AS a(elem)
                   WHERE  (a.elem ->> 'id' = '101') IS NOT TRUE)
WHERE  u.settings @> '[{"id":101}]';

fiddle

The added outer WHERE clause makes sure only rows are updated that actually change. You may have additional filters ...

This removes all objects from the array that have "id": 101. (There might be more than one.)

To only remove the first match:

UPDATE users u
SET    settings = u.settings
                - (SELECT a.ord::int-1
                   FROM   jsonb_array_elements(u.settings) WITH ORDINALITY a(elem, ord)
                   WHERE  a.elem ->> 'id' = '101'
                   LIMIT  1)
WHERE  u.settings @> '[{"id":101}]';

fiddle

Notably, do not repeat the table name in the correlated subquery, that would be expensive bloat - and require addtitional filters to link back to the updated table.

-1 is there because Postgres ordinality numbers start with 1 while JSON array elements start with 0.

About WITH ORDINALITY:

答案2

得分: 1

使用operator -,无需使用jsonb_set

这是一个经过优化的解决方案,因为它仅调用where一次。如果未找到数据,它将返回settings length,这样我们可以确保不会删除任何内容。

update users
set settings = 
    settings  - (
    select coalesce(
       (select position::int-1 as index
        from jsonb_array_elements(settings) 
        with ordinality arr(elem, position) 
        WHERE elem->>'id' = '100'
        LIMIT 1
       ), 
       jsonb_array_length(settings))
   )

- 运算符会删除具有指定索引的数组元素(负整数从末尾开始计数)。

演示在这里

英文:

Use operator - only, no need to use jsonb_set.

This is an optimized solution since it call where only once. if no data found it will return the settings length this way we will be sure nothing will be removed.

update users
set settings = 
    settings  - (
    select coalesce(
       (select position::int-1 as index
        from jsonb_array_elements(settings) 
        with ordinality arr(elem, position) 
        WHERE elem->>'id' = '100'
        LIMIT 1
       ), 
       jsonb_array_length(settings))
   )

The operator - Delete the array element with specified index (Negative integers count from the end).

Demo here

huangapple
  • 本文由 发表于 2023年3月15日 18:56:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/75743767.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定