2

I have a table called "Bookmarks" that contains several standard rows and also a JSONB column called "columnsettings"

The content of this JSONB column looks like this.

[
    {
        "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
    }
]

I am trying to write an update statement which would update this columnsettings by removing a specific node I specify. For example, I might want to update the columnsettings and remove just the node where data='field_2' as an example.

I have tried a number of things...I believe it will look something like this, but this is wrong.

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

What is the correct syntax to remove a node within a JSONB Array like this?

I did get a version working when there is a single row. This correctly updates the JSONB column and removes the node

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

However, I want it to apply to every row in the table. When there is more than 1 row, I get the error " more than one row returned by a subquery used as an expression"

How do I get this query to update all rows in the table?


UPDATED, the answer provided solved my issue.

I now have another JSONB column where I need to do the same filtering. The structure is a bit different, it looks likke this

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

I tried using the syntax the same way as follows:

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;

This gives an error: "cannot extract elements from an object"

I thought I had the syntax correct, but how should this line be formatted?

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

I tried this format as well to get to the array. This doesn't given an error, but it doesn't find any matches...even though there are records.

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;

UPDATED .

This query now seems to work if there is more than one "filter" in the array. However, if there is only 1 element in array which should be excluded, it doesn't remove the item.

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;
mike hennessy
  • 1,359
  • 1
  • 16
  • 35

1 Answers1

1

You can deconstruct, filter, and re-construct the JSONB array. Something like this should work:

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;

Using JSONB_ARRAY_ELEMENTS, you transform the JSONB array into rows, one per object, which you call el. Then you can access the data attribute to filter out the "field_2" entry. Finally, you group by id to put the remainign values back together, and update the corresponding row.


EDIT If your data is a nested array in an object, 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;
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
  • Hey @Ruben, I updated my question with a slightly different version of the JSONB format. Can you point out what I have wrong on this syntax to perform the same filtering on this different JSONB format? – mike hennessy Nov 24 '20 at 21:46
  • Thx, I tried this and it seems to work if there is more than one node in the array that matches. In this case, it correctly removes /fitlers out the node. However, if there is only one node in the array which matches what I'm trying to filter out, it ignores it and the item remains. any ideas on how to fix? – mike hennessy Nov 24 '20 at 23:10
  • I'd just filter out those cases in a seperate update statement. `WHERE JSONB_ARRAY_LENGTH(tabsettings->'filters') = 1 AND tabsettings->'filters'->0->>'field' = 'field_2'` and use the `-` operator to remove the key. See [the docs](https://www.postgresql.org/docs/11/functions-json.html) for more detail – Ruben Helsloot Nov 24 '20 at 23:26