Home > Software engineering >  Postgresql, JSONB, how to query a joined string from an array of objects
Postgresql, JSONB, how to query a joined string from an array of objects

Time:10-29

I've a JSONB inside a PostgreSQL table with this structure (more or less)

{
    "obj1": {
        "obj2": {
            "obj3": [
                {
                    "obj4": {
                        "obj": "A"
                    }
                },
                {
                    "obj4": {
                        "obj": "B"
                    }
                }
            ]
        }
    }
}

Then my obj3 is an array of objects and I wanna the obj inside obj4 separated by comma.

Thus what I really need is something like:

1 | A,B
2 | C,D
3 | NULL

I'm using PostgreSql 14. Any help is going to be appreciate.

and I've got this

select t.id, 
jsonb_path_query(t.b, '$."obj1"."obj2"."obj3"[*]."obj4"."obj"'::jsonpath) as obj5
from (
    values (1, '{"obj1":{"obj2":{"obj3":[{"obj4":{"obj":"A"}},{"obj4":{"obj":"B"}}]}}}'::jsonb), 
    (2, '{"obj1":{"obj2":{"obj3":[{"obj4":{"obj":"C"}},{"obj4":{"obj":"D"}}]}}}'::jsonb), 
    (3, '{}'::jsonb)
) t(id, b);

enter image description here


But the json_path_query multiply the rows and remove not found results as well...

CodePudding user response:

You need to group the resulting rows by t.id so that to group A & B, and C & D on the same row while using the string_agg function to group them in the same resulting column with ',' as separator.

But to do so, you need first to switch the jsonb_path_query function from the SELECT clause to the FROM clause while introducing a LEFT JOIN so that to keep the rows with no output from the jsonb_path_query function.

The solution is :

select t.id, string_agg(obj5->>'obj', ',') AS result
from (
    values (1, '{"obj1":{"obj2":{"obj3":[{"obj4":{"obj":"A"}},{"obj4":{"obj":"B"}}]}}}'::jsonb), 
    (2, '{"obj1":{"obj2":{"obj3":[{"obj4":{"obj":"C"}},{"obj4":{"obj":"D"}}]}}}'::jsonb), 
    (3, '{}'::jsonb)
) t(id, b)
left join lateral jsonb_path_query(t.b, '$.obj1.obj2.obj3[*].obj4') as obj5
  on TRUE
group by t.id;

see dbfiddle

CodePudding user response:

Inside-out: climb the object tree, flatten the array and then select/aggregate. DB fiddle

select id, (
    select string_agg(j->'obj4'->>'obj', ',')
    from jsonb_array_elements(b->'obj1'->'obj2'->'obj3') as j
  ) as objlist
from the_table;
id objlist
1 A,B
2 C,D
3

CodePudding user response:

For the sake of clarity/reuse, I'd create a function to convert the jsonb array to a Postgres array.

CREATE OR REPLACE FUNCTION jsonb_text_array(jsonb)
                   RETURNS text[]
                  LANGUAGE sql
                  PARALLEL SAFE
                 LEAKPROOF
                    STRICT
                        AS $$
  SELECT array_agg(t)
    FROM jsonb_array_elements_text($1) x(t)
  ;
$$;

Then this query should return what you want.

SELECT t.id
     , jsonb_text_array(jsonb_path_query_array(t.b, '$.obj1.obj2.obj3.obj4.obj')) AS obj5
  FROM ( VALUES
         (1, '{"obj1":{"obj2":{"obj3":[{"obj4":{"obj":"A"}},{"obj4":{"obj":"B"}}]}}}'::jsonb)
       , (2, '{"obj1":{"obj2":{"obj3":[{"obj4":{"obj":"C"}},{"obj4":{"obj":"D"}}]}}}'::jsonb)
       , (3, '{}'::jsonb)
       ) t(id, b);

If you really want a string returned instead of an array, change the function to return text instead of text[] and use string_agg(t, ',') instead of array_agg(t).

  • Related