Héctor
Héctor

Reputation: 26054

Error deleting json object from array in Postgres

I have a Postgres table timeline with two columns:

This is the structure of items json field:

[
  {
    itemId: "12345",
    text: "blah blah"
  },
  //more items with itemId and text
]

I need to delete all the items where itemId equals a given value. e.g. 12345

I have this working SQL:

UPDATE timeline 
SET items = items::jsonb - cast(( 
  SELECT position - 1 timeline, jsonb_array_elements(items::jsonb)
  WITH ORDINALITY arr(item_object, position) 
  WHERE item_object->>'itemId' = '12345') as int)

It works fine. It only fails when no items are returned by the subquery i.e. when there are no items whose itemId equals '12345'. In those cases, I get this error:

null value in column "items" violates not-null constraint

How could I solve this?

Upvotes: 1

Views: 118

Answers (2)

Mike Organek
Mike Organek

Reputation: 12484

The problem is that when null is passed to the - operator, it results in null for the expression. That not only violates your not null constraint, but it is probably also not what you are expecting.

This is a hack way of getting past it:

UPDATE timeline 
SET items = items::jsonb - coalesce(
   cast(( 
     SELECT position - 1 timeline, jsonb_array_elements(items::jsonb)
     WITH ORDINALITY arr(item_object, position) 
     WHERE item_object->>'itemId' = '12345') as int), 99999999)

A more correct way to do it would be to collect all of the indexes you want to delete with something like the below. If there is the possibility of more than one userId: 12345 within a single user_id row, then this will either fail or mess up your items(I have to test to see which), but at least it updates only rows with the 12345 records.

WITH deletes AS (
  SELECT t.user_id, e.rn - 1 as position
    FROM timeline t
   CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(t.items)
          WITH ORDINALITY as e(jobj, rn)
   WHERE e.jobj->>'itemId' = '12345'
)
UPDATE timeline 
   SET items = items - d.position
  FROM deletes d
 WHERE d.user_id = timeline.user_id;

Upvotes: 2

Akhilesh Mishra
Akhilesh Mishra

Reputation: 6130

Try this:

update timeline 
set items=(select 
           json_agg(j) 
          from json_array_elements(items) j 
          where j->>'itemId' not in ( '12345')
          );

DEMO

Upvotes: 2

Related Questions