Posted by Kyle Hankinson March 14, 2024
PostgreSQL's JSONB type lets you store JSON documents alongside relational data, with full indexing and query support. It is one of PostgreSQL's most powerful features.
PostgreSQL offers two JSON types:
JSON: Stores the raw text. Preserves whitespace and key order. No indexing.JSONB: Stores a parsed binary format. Slightly slower to insert, much faster to query. Supports indexes.Use JSONB unless you have a specific reason to preserve the original JSON formatting.
CREATE TABLE events (
id SERIAL PRIMARY KEY,
data JSONB NOT NULL
);
INSERT INTO events (data) VALUES
('{"type": "click", "page": "/home", "user": {"id": 42, "name": "Alice"}}');
PostgreSQL provides several operators for navigating JSON:
| Operator | Returns | Example |
|---|---|---|
-> |
JSON element | data -> 'user' returns {"id": 42, "name": "Alice"} |
->> |
Text value | data ->> 'type' returns click |
#> |
JSON at path | data #> '{user,name}' returns "Alice" |
#>> |
Text at path | data #>> '{user,name}' returns Alice |
-- Get all click events
SELECT * FROM events WHERE data ->> 'type' = 'click';
-- Get the user name from nested JSON
SELECT data #>> '{user,name}' AS user_name FROM events;
The @> operator checks if the left JSONB contains the right JSONB:
-- Find events where user id is 42
SELECT * FROM events
WHERE data @> '{"user": {"id": 42}}';
This is not only cleaner than chaining -> operators, it can also use GIN indexes for fast lookups.
| Operator | Meaning |
|---|---|
? |
Does the key exist? |
?| |
Do any of these keys exist? |
?& |
Do all of these keys exist? |
-- Find events that have a "page" key
SELECT * FROM events WHERE data ? 'page';
A GIN index makes JSONB queries fast:
-- General-purpose: supports @>, ?, ?|, ?&
CREATE INDEX idx_events_data ON events USING GIN (data);
-- Optimized for @> containment queries only (smaller index)
CREATE INDEX idx_events_data ON events USING GIN (data jsonb_path_ops);
For queries on a specific key, a B-tree index on the extracted value is more efficient:
-- Index a specific field for equality/range queries
CREATE INDEX idx_events_type ON events ((data ->> 'type'));
-- Set a key
UPDATE events SET data = jsonb_set(data, '{page}', '"/about"') WHERE id = 1;
-- Remove a key
UPDATE events SET data = data - 'page' WHERE id = 1;
-- Merge with concatenation
UPDATE events SET data = data || '{"source": "mobile"}' WHERE id = 1;
JSONB is a good fit for:
Stick with regular columns for data you frequently filter, join, or aggregate on — relational columns are still faster for those operations.
SQLPro Studio displays JSONB columns in query results so you can inspect the full JSON structure. You can write and test all of these JSONB queries in the query editor with syntax highlighting and autocomplete.
About the author — Kyle Hankinson is the founder and sole developer of SQLPro Studio and the Hankinsoft Development suite of database tools. He has been building native macOS and iOS applications since 2010.
Try SQLPro Studio — A powerful database manager for MySQL, PostgreSQL, Microsoft SQL Server, SQLite, Oracle, and Snowflake. Available on macOS, iOS, and Windows.
Download Free Trial View Pricing Compare