Ndifreke Ekott

Thoughts, stories, ideas and programming

07 Aug 2024

Understanding Operations on Json Data Types With Postgresql

This is a follow-up article to an earlier published one titled “**Understanding and Working with JSON Data Types with PostgreSQL”.**

PostgreSQL provides some operators for working with json and jsonb data. The best way to review these operators is by memorising their use. There aren’t names but symbols so hold on tight, while I write down a couple I think you are more likely to use them right away.

JSON Data Type Operations

A few of the operators apply to both json and json data types. I will place both syntaxes to indicate that an operator applies to both.

jsonb -> integer -> jsonb / json -> integer -> json

Fetch the nth element from a JSON array. Negative nth starts from the end. Remember arrays start from a 0 index.

SELECT '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::jsonb -> 2
# Output: {"c":"baz"}

SELECT '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::jsonb -> -1
# Output: {"c":"baz"}

SELECT '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::jsonb -> -3
# Output: {"a":"foo"}

jsonb -> text → jsonb / json -> text → json

Get the object field with the given key. Trying to access a key that doesn’t exists will return a null.

SELECT '{"person": {"age": 23, "height": 1.80}}'::jsonb -> 'person'
# Output: {"age": 23, "height": 1.80}

jsonb ->> integer → text / json ->> integer → text

Get the nth element of a JSON array as text

SELECT '[1, 2, 3, 4, 5]'::jsonb ->> 2
# Output: 3

jsonb ->> text → text / json ->> text → text

Get the object field with the given key as text.

SELECT '{"age": 23, "height": 1.80}'::jsonb ->> 'age'
# Output: 23

jsonb #> text[] → jsonb / json #> text[] → json

Get the sub-element from the specified path where path element can be field keys or array index.

SELECT '{"person": {"age": 23, "music": {"genre": ["pop", "kpop", "jazz"]}}}'::json #> '{person,music,genre}'
# Output: ["pop", "kpop", "jazz"]

SELECT '{"person": {"age": 23, "music": {"genre": ["pop", "kpop", "jazz"]}}}'::json #> '{person,music,genre, 1}'
# Output: "kpop"

jsonb #>> text[] → text / json #>> text[] → text

Fetch the sub-element as text.

SELECT '{"person": {"age": 23, "music": {"genre": ["pop", "kpop", "jazz"]}}}'::json #>> '{person,music,genre, 1}'
# Output: kpop

jsonb @> jsonb → boolean

Is the object on the left contain the object on the right?

SELECT '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb # Output: true

jsonb <@ jsonb → boolean

The inverse of the above. Does the object on the right contain the object on the left?

SELECT '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb # Output: true

jsonb ? text → boolean

Used to check if a string exists at the top level as key.

SELECT '{"age":17, "height":2.0}'::jsonb ? 'age'
# Output: true

jsonb ?| text[] → boolean

Do any of the strings in the array exists as top level keys?

SELECT '{"age":17, "middle_name": "middleName", "first_name": "Fred"}'::jsonb ?| array['middle_name', 'high_score']
# Output: true

jsonb ?& text[] → boolean

Do all the strings in the array exists as keys?

SELECT '{"first_name": "Alfred", "last_name": "Bruno"}'::jsonb ?& array['first_name', 'last_name']
# Output: true

jsonb - text → jsonb

Delete key from JSON object.

SELECT '{"a": "b", "c": "d"}'::jsonb - 'a'
#Output: {"c": "d"}

jsonb - text[] → jsonb

Delete all keys in the array list.

select '{"a": "b", "c": "d", "e": "f"}'::jsonb - '{a,c}'::text[]
# Output: {"e": "f"}

jsonb @@ jsonpath → boolean

Does the jsonpath return any result?

SELECT '{"a":[1,2,3,4,5]}'::jsonb @@ '$.a[*] > 2'

There are lot of JSON operations and it doesn’t make sense to list all of them as you will barely use them in most of your day to day.

For a complete list of JSON based operations visit the documentation page: JSON Functions and Operators.

How about Indexing?

It only makes sense that we should have a way to index routinely searched keys in a JSON data column. Postgres makes it possible to use GIN indexes to efficiently search for keys and values. Postgres provides two GIN operator classes wit different trade offs.

The default GIN index for jsonb allows querying using existence operators: ?, ?| and ?&, containment operators: @> and jsonpath match operators @? and @@.

Assuming we have a table that stores some API response from a third party data provider that holds records on books, we could create an appropriate index for searching for tags.

{
  "id": 123,
  "title": "Book title",
  "tags": ["Drama", "Fiction", "Love"],
  "Author": "Andrew Parks"
}

A common search would be to look for all books that have a certain tag. The syntax for the GIN index is described below.

CREATE INDEX idxgintags ON books USING GIN ((jdoc -> 'tags'));

This allows us to write an efficient query to easily fetch all books with Fiction tags.

SELECT * FROM books where jdoc->'tags' ? 'Fiction';

The above query will get the database to use the tag index to speed up your search.

To learn more about GIN index for JSON data types including json_path_ops type index, read the docs - Jsonb Indexing.

Conclusion

Understanding and working with JSON data types in PostgreSQL can greatly enhance your ability to store and query complex data structures. PostgreSQL provides a variety of operators and functions for manipulating json and jsonb data, allowing for efficient and flexible data management. Additionally, using GIN indexes can significantly improve the performance of JSON queries, particularly when working with large datasets. By familiarizing yourself with these tools and techniques, you can leverage the full potential of JSON in PostgreSQL for your data-driven applications.