如何从 Postgres 中所有 table 行的 JSONB 数组中删除节点?

How to delete a node from a JSONB Array across all table rows in Postges?

我有一个名为“书签”的 table,其中包含多个标准行和一个名为“columnsettings”的 JSONB 列

此 JSONB 列的内容如下所示。

[
    {
        "data": "id",
        "width": 25
    },
    {
        "data": "field_1",
        "width": 125
    },
    {
        "data": "field_12",
        "width": 125
    },
    {
        "data": "field_11",
        "width": 125
    },
    {
        "data": "field_2",
        "width": 125
    },
    {
        "data": "field_7",
        "width": 125
    },
    {
        "data": "field_8",
        "width": 125
    },
    {
        "data": "field_9",
        "width": 125
    },
    {
        "data": "field_10",
        "width": 125
    }
]

我正在尝试编写一个更新语句,该语句将通过删除我指定的特定节点来更新此列设置。例如,我可能想更新列设置并仅删除 data='field_2' 作为示例的节点。

我已经尝试了很多东西...我相信它看起来像这样,但这是错误的。

update public."Bookmarks"
set columnsettings = 
    jsonb_set(columnsettings, (columnsettings->'data') - 'field_2');

像这样在 JSONB 数组中删除节点的正确语法是什么?

当只有一行时,我确实得到了一个版本。这会正确更新 JSONB 列并删除节点

UPDATE public."Bookmarks" SET columnsettings = columnsettings - (select position-1 from public."Bookmarks", jsonb_array_elements(columnsettings) with ordinality arr(elem, position) WHERE elem->>'data' = 'field_2')::int

但是,我希望它应用于 table 中的每一行。当多于 1 行时,出现错误“用作表达式的子查询返回多于一行”

如何让这个查询更新 table 中的所有行?


已更新,提供的答案解决了我的问题。

我现在有另一个 JSONB 列,我需要在其中进行相同的过滤。结构有点不同,看起来像这样

{
    "filters": [
        {
            "field": "field_8",
            "value": [
                1
            ],
            "header": "Colors",
            "uitype": 7,
            "operator": "searchvalues",
            "textvalues": [
                "Red"
            ],
            "displayfield": "field_8_options"
        }
    ],
    "rowHeight": 1,
    "detailViewWidth": 1059
}

我尝试使用如下相同的语法:

UPDATE public."Bookmarks"
SET tabsettings = filtered_elements.tabsettings
FROM (
    SELECT bookmarkid, JSONB_AGG(el) as tabsettings
    FROM public."Bookmarks",
         JSONB_ARRAY_ELEMENTS(tabsettings) AS el
    WHERE el->'filters'->>'field' != 'field_8'
    GROUP BY bookmarkid
) AS filtered_elements
WHERE filtered_elements.bookmarkid = public."Bookmarks".bookmarkid;

这给出了一个错误:“无法从对象中提取元素”

我以为我的语法是正确的,但是应该如何格式化这一行?

WHERE el->'filters'->>'field' != 'field_8'

我也尝试过这种格式来获取数组。这没有给出错误,但它没有找到任何匹配项......即使有记录。

UPDATE public."Bookmarks"
SET tabsettings = filtered_elements.tabsettings
FROM (
    SELECT bookmarkid, JSONB_AGG(el) as tabsettings
    FROM public."Bookmarks",
         JSONB_ARRAY_ELEMENTS(tabsettings->'filters') AS el
    WHERE el->>'field' != 'field_8'
    GROUP BY bookmarkid
) AS filtered_elements
WHERE filtered_elements.bookmarkid = public."Bookmarks".bookmarkid;

已更新。

如果数组中有多个“过滤器”,则此查询现在似乎可以工作。 但是,如果数组中只有 1 个元素应该被排除,它不会删除该项目。

UPDATE public."Bookmarks"
SET tabsettings = filtered_elements.tabsettings
FROM (
    SELECT bookmarkid,
           tabsettings || JSONB_BUILD_OBJECT('filters', JSONB_AGG(el)) as tabsettings
    FROM public."Bookmarks",
         -- this must be an array
         JSONB_ARRAY_ELEMENTS(tabsettings->'filters') AS el
    WHERE el->>'field' != 'field_8'
    GROUP BY bookmarkid
) AS filtered_elements
WHERE filtered_elements.bookmarkid =  public."Bookmarks".bookmarkid;

您可以解构、过滤和重新构造 JSONB 数组。这样的事情应该有效:

UPDATE bookmarks
SET columnsettings = filtered_elements.columnsettings
FROM (
    SELECT id, JSONB_AGG(el) as columnsettings
    FROM bookmarks,
         JSONB_ARRAY_ELEMENTS(columnsettings) AS el
    WHERE el->>'data' != 'field_2'
    GROUP BY id
) AS filtered_elements
WHERE filtered_elements.id = bookmarks.id;

使用 JSONB_ARRAY_ELEMENTS,您将 JSONB 数组转换为行,每个对象一行,您称之为 el。然后您可以访问 data 属性以过滤掉“field_2”条目。最后,您按 id 分组,将剩余的值放回一起,并更新相应的行。


编辑 如果您的数据是对象中的嵌套数组,override the object on the specific key:

UPDATE bookmarks
SET tabsettings = filtered_elements.tabsettings
FROM (
    SELECT id,
           tabsettings || JSONB_BUILD_OBJECT('filters', JSONB_AGG(el)) as tabsettings
    FROM bookmarks,
         -- this must be an array
         JSONB_ARRAY_ELEMENTS(tabsettings->'filters') AS el
    WHERE el->>'field' != 'field_2'
    GROUP BY id
) AS filtered_elements
WHERE filtered_elements.id = bookmarks.id;