1

I have a table named guest_group in a Postgres DB and a column named custom_fields with the following values: (example)

[
   {
      "value":"Frau Barbara Test",
      "display_name":"Name",
      "servicio_tags":[
         "full-name"
      ]
   },
   {
      "value":"118",
      "display_name":"Zimmernummer",
      "servicio_tags":[
         "room-number"
      ]
   },
   {
      "value":"likes postgreSQL",
      "display_name":"Traces",
      "servicio_tags":[
         "trace"
      ]
   }
]

I want to update all rows with the

  • value equals to Frau Barbara Test and where the servicio_tags array is equals to ["full-name"]
  • plus where the value equal to 118 where the servicio_tags array is equal to ["room-number"]

The Update should add a new object to the JSONB:

{
   "value":"likes JSONB",
   "display_name":"Traces",
   "servicio_tags":[
      "trace"
   ]
}

My try so far:

UPDATE guest_group 
SET custom_fields = 
jsonb_insert('[{"value": "Frau Barbara Test", "display_name": "Name", "servicio_tags": ["full-name"]}, {"value": "118", "display_name": "Zimmernummer", "servicio_tags": ["room-number"]}]'::jsonb,
'{0}', 
'{"value": "likes JSONB", "display_name": "Traces", "servicio_tags": ["trace"]}'::jsonb, 
true)
 where custom_fields::text like '%"Frau Barbara Test"%'
 and custom_fields::text like '%"118 "%'

This try changes the value in column of the specific row to the following:

[  
   {  
      "value":"Frau Barbara Test",
      "display_name":"Name",
      "servicio_tags":[  
         "full-name"
      ]
   },
   {  
      "value":"likes JSONB",
      "display_name":"Traces",
      "servicio_tags":[  
         "trace"
      ]
   },
   {  
      "value":"118",
      "display_name":"Zimmernummer",
      "servicio_tags":[  
         "room-number"
      ]
   }
]

So the:

   {
      "value":"likes postgreSQL",
      "display_name":"Traces",
      "servicio_tags":[
         "trace"
      ]
   }

gets lost. How can do the update without loosing this data?

Anton Hoerl
  • 189
  • 1
  • 10
  • 1
    Do you need the `servicio_tags` array to **equal** that exact value, or do you just need it to contain that respective string (possibly amongst others)? – Bergi Nov 02 '19 at 17:04
  • The `servicio_tags` array has to be equal to that exact value. – Anton Hoerl Nov 02 '19 at 17:05

2 Answers2

1

I would recommend not to compare JSON as strings. Instead, use the vast array of JSON operators and functions. To find rows that contain Mrs. Test in a value, you could for example use the @> operator:

WHERE '[{"value": "Frau Barbara Test"}]'::jsonb <@ custom_fields

See also Postgresql query array of objects in JSONB field.

However, this doesn't yet check that it's the same object that has a full-name tag, so we might try

WHERE '[{"value": "Frau Barbara Test", "servicio_tags": ["full-name"]}]'::jsonb <@ custom_fields

That's much better than text comparison, but still not exactly what you asked for. Due to the semantics of the @> operator, it checks the tags array to contain a "full-name" string, not to have it as its single element.

To do that, it gets a bit more complicated, involving a subquery on the jsonb_array_elements and accessing its json components explicitly for comparison:

SELECT *
FROM guest_group
WHERE EXISTS(
  SELECT 1
  FROM jsonb_array_elements(custom_fields) AS el
  WHERE el->>'value' = 'Frau Barbara Test'
    AND el->'servicio_tags' = '["full-name"]'::jsonb)

You could also mix the two approaches using ANY:

SELECT *
FROM guest_group
WHERE '{"value": "Frau Barbara Test"}' <@ ANY(
  SELECT el
  FROM jsonb_array_elements(custom_fields) AS el
  WHERE el->'servicio_tags' = '["full-name"]'::jsonb)

(online demo)

Now equipped with these tools, you can add a second condition to match the room number. Then use

UPDATE guest_group 
SET custom_fields = jsonb_insert(custom_fields, '{0}', '{"value": "likes JSONB", "display_name": "Traces", "servicio_tags": ["trace"]}'::jsonb, true)
WHERE …
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

This seems to work:

UPDATE guest_group 
SET custom_fields = 
jsonb_insert(custom_fields::jsonb,'{0}','{"value": "likes JSONB", "display_name": "Traces", "servicio_tags": ["trace"]}'::jsonb) 
WHERE custom_fields::text like '%"Frau Barbara Test"%'
AND custom_fields::text like '%"117"%'
Anton Hoerl
  • 189
  • 1
  • 10