3

Let's say I have a jsonb column called meta with this value:

{
  "foo": {
    "bar": "baz"
  }
}

When I want to filter by the meta.foo.bar value, I can do this:

select * from tbl where meta->foo->>bar = 'baz';

Now let's say I want to make the foo an array of objects instead:

{
  "foo": [
    { "bar": "baz 1" },
    { "bar": "baz 2" }
  ]
}

How can I filter by the meta.foo.*.bar values? Tried various combinations of this -> syntax without any luck. Only thing that worked was searching by a specific index in the array, but I want to check all the items, not just one.

FWIW I'd also like to support multiple nested arrays. The use case is filtering by JSON properties in an ORM (http://mikro-orm.io/), so it needs to be dynamic.

2 Answers 2

4

You can do that with the jsonb containment operator @>:

SELECT '{"foo": [{"bar": "baz 1"}, {"bar": "baz 2"}]}'::jsonb @>
       '{"foo": [{"bar": "baz 1"}]}'::jsonb;

Alternatively, you could use a JSONPATH query:

SELECT jsonb_path_exists(
          '{"foo": [{"bar": "baz 1"}, {"bar": "baz 2"}]}',
          '$.foo[*] ? (@.bar == "baz 2")'
       );

To search for bar no matter how deep it is nested, you can

SELECT jsonb_path_exists(
          '{"foo": [{"bar": "baz 1"}, {"bar": "baz 2"}]}',
          '$.**.bar ? (@ == "baz 2")'
       );

If you want to search with = 'baz 2', you will habe to unnest the array:

SELECT *
FROM jsonb_array_elements(
        '{"foo": [{"bar": "baz 1"}, {"bar": "baz 2"}]}'::jsonb -> 'foo'
     ) AS elem(j)
WHERE j->>'bar' = 'baz 2';

But then you have to know exactly where in the JSON you want to search.

Sign up to request clarification or add additional context in comments.

10 Comments

Which is not really what I am looking for, this would mean I have to construct the right hand side. In my context the right hand side is just a string (e.g. baz 1), not a JSON.
I am looking for a generic solution, as I am targetting multiple databases, not just pg. With mysql something like this should be possible: json_extract(meta, '$.foo[*].bar') = 'baz 1', maybe there is a similar way with some function?
Perhaps a JSONPATH query would make it simpler? Things do become complicated when you use JSON.
Yeah also saw that way already, but it is quite problematic for me as, again, I'd have to dynamically construct the right hand side, which can be much more complicated, e.g. with operators. I was looking for a way where the right hand side would stay as simple string like baz 1.
Also with this approach, how would nested arrays (of objects) work?
|
0

UPDATE 2: Based on additional information in the comments, if you want to use other operators (e.g. LIKE or >=), then you could do something around the following lines:

SELECT * FROM tbl
WHERE EXISTS (
  SELECT 1
  FROM jsonb_path_query(meta, '$.foo[*].bar') extracted
  WHERE extracted = '"baz 1"'
);

Please note that the filter value must be JSON encoded (therefore the " quotes around the string value).

Similar results can be achieved using JOINs or WITH CTE's but one must be careful about JOIN-style row duplication.


UPDATE 1: Thinking about it, you may want something like this:

SELECT * FROM tbl
WHERE jsonb_path_query_array(meta, '$.foo[*].bar') ? 'baz 1';

My original answer:

Since Postgres 12 you should be able to use the @@ jsonpath operator:

SELECT * FROM tbl WHERE meta @@ '$.foo[*].bar == "baz 1"';

2 Comments

This looks interesting, but I want to be able to use operators, e.g. like or >=, which is apparently not possible this way
@MartinAdámek I see, then you may want to extract the data first using json_path_query and use EXISTS/JOIN/WITH for filtering. See the updated answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.