Search code examples
sqlpostgresqlnestedjsonbpostgresql-12

Postgresql search if exists in nested jsonb


I'm new with jsonb request and i got a problem. Inside an 'Items' table, I have 'id' and 'data' jsonb. Here is what can look like a data:

[
  {
    "paramId": 3,
    "value": "dog"
  },
  {
    "paramId": 4,
    "value": "cat"
  },
  {
    "paramId": 5,
    "value": "fish"
  },
  {
    "paramId": 6,
    "value": "",
    "fields": [
      {
        "paramId": 3,
        "value": "cat"
      },
      {
        "paramId": 4,
        "value": "dog"
      }
    ]
  },
  {
    "paramId": 6,
    "value": "",
    "fields": [
      {
        "paramId": 5,
        "value": "cat"
      },
      {
        "paramId": 3,
        "value": "dog"
      }
    ]
  }
]

The value in data is always an array with object inside but sometimes the object can have a 'fields' value with objects inside. It is maximum one level deep.

How can I select the id of the items which as for example an object containing "paramId": 3 and "value": "cat" and also have an object with "paramId": 5 and "value" LIKE '%ish%'.

I already have found a way to do that when the object is on level 0

SELECT i.*
FROM items i
JOIN LATERAL jsonb_array_elements(i.data) obj3(val) ON obj.val->>'paramId' = '3'
JOIN LATERAL jsonb_array_elements(i.data) obj5(val) ON obj2.val->>'paramId' = '5'
WHERE obj3.val->>'valeur' = 'cat'
AND obj5.val->>'valeur' LIKE '%ish%';

but I don't know how to search inside the fields array if fields exists.

Thank you in advance for you help.

EDIT:
It looks like my question is not clear. I will try to make it better.

What I want to do is to find all the 'item' having in the 'data' column objects who match my search criteria. This without looking if the objects are at first level or inside a 'fields' key of an object. Again for example. This record should be selected if I search:

  • 'paramId': 3 AND 'value': 'cat
  • 'paramId': 4 AND 'value': LIKE '%og%'

the matching ones are in the 'fields' key of the object with 'paramId': 6 and I don't know how to do that.


Solution

  • This can be expressed using a JSON/Path expression without the need for unnesting everything

    To search for paramId = 3 and value = 'cat'

    select *
    from items
    where data @? '$[*] ? ( (@.paramId == 3 && @.value == "cat") || exists( @.fields[*] ? (@.paramId == 3 && @.value == "cat")) )'
    

    The $[*] part iterates over all elements of the first level array. To check the elements in the fields array, the exists() operator is used to nest the expression. @.fields[*] iterates over all elements in the fields array and applies the same expression again. I don't see a way how repeating the values could be avoided though.

    For a "like" condition, you can use like_regex:

    select *
    from items
    where data @? '$[*] ? ( (@.paramId == 4 && @.value like_regex ".*og.*") || exists( @.fields[*] ? (@.paramId == 4 && @.value like_regex ".*og.*")) )'