The JSONB type enables you to store and query nested JSON-like data in Postgres. With JSONB, you can to store arbitrarily complex objects and arrays in your Postgres tables, as well as query based on properties in those objects and arrays. You can even use GIN indexes to index nested properties within JSONB objects.
Steps
- Set up a table with a JSONB column
- Insert and retrieve JSONB data
- Query based on JSONB fields
- Document store using Sequelize ORM
- Query arrays and objects in JSONB
- Type casting in JSONB queries
- Update and modify JSONB data
- Index JSONB fields using GIN indexes
Set up a table with a JSONB column
To use Postgres as a document store, you can create a table with two columns: an id
and a data
property that is of type JSONB
.
You can run the following CREATE TABLE
statement in the Neon SQL Editor or from a client such as psql
that is connected to Neon.
JSONB columns can store any JSON object, including objects, arrays, and even nested objects.
Insert and retrieve JSONB data
Run the following SQL to insert two new rows into the documents
table. These rows will have data
columns with slightly different properties: the first row has a steps
property, and the second row has a nested object property, author
.
You can then load rows from the documents
collection by id
. For example, you can load the "Neon and JSONB" row using the following query.
Query based on JSONB fields
You can also query based on properties in the JSONB column using the ->>
operator, which extracts values from the JSONB column.
For example, you can load all documents with a given title
property using the following query.
Note the quotes around title
in the WHERE
clause.
You can also query based on nested properties.
For example, the following query returns all documents whose author
property is an object with a name
property equal to 'John Smith'.
Document store using Sequelize ORM
Many developers use Postgres through an ORM, like Sequelize in Node.js.
ORMs often provide neat syntactic shortcuts for working with JSONB.
For example, the following Node.js code shows how you can connect to the existing documents
table from previous examples using Sequelize.
You can then create rows in the documents
collection using the following:
Finally, you can find documents by the author's name using the following query.
Note that Sequelize takes care of converting data.author.name
to data->'author'->>'name'
under the hood.
You can read more about working with JSONB in Sequelize Prisma, and Objection.js on their respective documentation sites.
Query arrays and objects in JSONB
Postgres has several operators that are useful for working with JSONB, including the @>
operator, which checks if a given property contains the given value.
With arrays, @>
can check whether the array contains a given value.
For example, the following query returns all documents whose tags
property contains the string "JSONB".
Note that the right-hand side of @>
is a JSON string.
With objects, @>
can check whether the document contains one or more properties.
For example, the following query returns all documents whose author
property has author
equal to 'John Smith' and age
equal to 30.
The query above is equivalent to this query:
Type casting in JSONB queries
Operators like =
and @>
are fairly easy to work with: they don't throw any errors if the JSONB property has the wrong type.
However, things get a bit more tricky if you want to find all documents whose author
's age
property is greater than 25.
For example, this query throws an "operator does not exist" error:
You need to explicitly cast age
to an int
type for the above query to run, as shown here:
Depending on your data, you may need to add extra checks to avoid throwing an error if a document has an age
property that can't be casted to an int.
The following query explicitly checks if age
is a numeric string before attempting to cast to an int
.
Update and modify JSONB data
You can also update individual properties within your JSONB document without overwriting the entire document using the jsonb_set()
function.
For example, the following code updates the author.age
property to 35 for all documents whose author.name
property is "John Smith".
Note that jsonb_set()
expects the nested property name separated by commas (,
), not dots (.
).
Index JSONB fields using GIN indexes
GIN indexes allow you to index JSONB properties, which can make your queries faster as your data grows. This query shows how you can create a GIN index on the data
property:
To test out the GIN index, let's first insert 100 documents, 1 of which has author.name
set to "John Smith", and 99 that do not. Sometimes Postgres decides to skip using indexes and use a sequential scan instead when a query matches most of the table.
Next, you can run an EXPLAIN ANALYZE
query (or just click the "Explain" button in the Neon SQL Editor) to confirm that Postgres is using your GIN index.
Note that the query above uses the containment operator @>
, not WHERE data->'author'->>'name' = 'John Smith'
. GIN indexes only support certain operators with JSONB data, including @>
.
The EXPLAIN ANALYZE
query should produce output that resembles the following. The Bitmap Index Scan means that Postgres is using a GIN index rather than a sequential scan to answer the query.