Postgres jsonb: querying multi level

Viewed 197

In a Postgres (9+) table there is a column of type jsonb with the following json:

{
   "dynamicFields":[
      {
         "name":"040",
         "subfields":[
            {
               "name":"a",
               "value":"abc"
            },
            {
               "name":"a",
               "value":"xyz"
            }
         ]
      }
   ]
}

I would like to write a query that return only the rows where the field name equals 040 and subfield a equals xyz.

This is as far as I got, so far:

select e.obj from my_table
cross join lateral jsonb_array_elements(my_column-> 'dynamicFields') as e(obj)
where e.obj ->> 'name' = '040' and e.obj ->> 'subfields' @> '{"name": "a", "value": "xyz"}'::jsonb

How should this query be to achieve this?

2 Answers

e.obj ->> 'subfields' has a text result. You'll want to use e.obj -> 'subfields' that returns the jsonb value where the @> operator works. Also the containment checks needs to have another array as the right hand side, so that it will test whether all values in the right array are contained in the left array - it doesn't work to pass the element object directly.

select e.obj from my_table
cross join lateral jsonb_array_elements(my_column-> 'dynamicFields') as e(obj)
where e.obj ->> 'name' = '040' and e.obj -> 'subfields' @> '[{"name": "a", "value": "xyz"}]'::jsonb
--                                        ^                 ^                             ^

(online demo)

As you have an equality condition you can use the "contains" operator directly by providing a JSON value of what you want. There is no need to unnest the arrays.

select *
from my_table
where my_column -> 'dynamicFields' @> '[{"name": "040", "subfields": [{"name":"a", "value": "xyz"}]}]'

Starting with Postgres 12 an alternative is to a SQL/JSON path operator:

select *
from my_table
where my_column @? '$.dynamicFields[*] ? (@.name == "040").subfields[*] ? (@.name == "a" && @.value == "xyz")'
Related