We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Querying Nested JSON Data
Now that you know the basic operators (->, ->>, #>, @>), let's explore PostgreSQL's rich set of JSON functions for working with complex, nested data structures.
jsonb_extract_path() and jsonb_extract_path_text()
These functions are the functional equivalents of the #> and #>> operators. They navigate a path through a JSONB document.
jsonb_extract_path() -- Returns JSONB
-- Equivalent to: preferences #> '{notifications}'
SELECT
username,
jsonb_extract_path(preferences, 'notifications') AS notif_settings
FROM user_settings;
jsonb_extract_path_text() -- Returns Text
-- Equivalent to: preferences #>> '{notifications,email}'
SELECT
username,
jsonb_extract_path_text(preferences, 'notifications', 'email') AS email_notif
FROM user_settings;
Why Use Functions Instead of Operators?
The function form has a few advantages:
- Readability with deep paths: Comma-separated arguments can be clearer than array syntax
- Dynamic paths: Easier to construct in procedural code
- Standard SQL: More portable across database systems
-- Compare readability for a deep path:
-- Operator style:
SELECT preferences #>> '{editor,font_size}' FROM user_settings;
-- Function style:
SELECT jsonb_extract_path_text(preferences, 'editor', 'font_size') FROM user_settings;
jsonb_object_keys()
This set-returning function expands all top-level keys of a JSONB object into separate rows.
-- List all top-level preference keys for each user
SELECT
username,
jsonb_object_keys(preferences) AS pref_key
FROM user_settings;
| username | pref_key |
|---|---|
| alice | theme |
| alice | notifications |
| alice | editor |
| bob | theme |
| bob | notifications |
| bob | editor |
| ... | ... |
Finding Users with Specific Keys
-- Which users have an "editor" key in their preferences?
SELECT DISTINCT username
FROM user_settings, jsonb_object_keys(preferences) AS k
WHERE k = 'editor';
Counting Keys per User
SELECT
username,
COUNT(k) AS num_preference_keys
FROM user_settings, jsonb_object_keys(preferences) AS k
GROUP BY username;
jsonb_each() and jsonb_each_text()
These functions expand a JSONB object into a set of key-value pair rows.
jsonb_each() -- Values as JSONB
SELECT
username,
kv.key,
kv.value
FROM user_settings,
jsonb_each(preferences) AS kv;
| username | key | value |
|---|---|---|
| alice | theme | "dark" |
| alice | notifications | {"email": true, "push": false} |
| alice | editor | {"font_size": 14, "tab_size": 4} |
| bob | theme | "light" |
| ... | ... | ... |
jsonb_each_text() -- Values as Text
SELECT
username,
kv.key,
kv.value
FROM user_settings,
jsonb_each_text(preferences) AS kv;
The values are now plain text strings. Nested objects are converted to their text representation.
Using jsonb_each for Flexible Queries
This is powerful for searching across all settings:
-- Find any preference key that has the value "dark"
SELECT username, kv.key, kv.value
FROM user_settings,
jsonb_each_text(preferences) AS kv
WHERE kv.value = 'dark';
jsonb_array_elements()
If your JSONB data contains arrays, jsonb_array_elements() expands each array element into a separate row.
While the user_settings table may not have arrays in its current structure, here is how it works conceptually:
-- Expanding a JSON array literal
SELECT jsonb_array_elements('[1, 2, 3, "four", {"five": 5}]'::jsonb);
| jsonb_array_elements |
|---|
| 1 |
| 2 |
| 3 |
| "four" |
| {"five": 5} |
jsonb_array_elements_text()
Returns array elements as text instead of JSONB:
SELECT jsonb_array_elements_text('["apple", "banana", "cherry"]'::jsonb);
Practical Use: Querying Array Contents
If a user's profile had a tags array:
-- Hypothetical: find users with a specific tag
SELECT username
FROM user_settings,
jsonb_array_elements_text(profile -> 'tags') AS tag
WHERE tag = 'admin';
jsonb_typeof()
Returns the type of a JSONB value as a text string. Useful for inspecting unknown structures.
SELECT
username,
jsonb_typeof(preferences) AS prefs_type,
jsonb_typeof(preferences -> 'theme') AS theme_type,
jsonb_typeof(preferences -> 'notifications') AS notif_type,
jsonb_typeof(preferences -> 'editor' -> 'font_size') AS font_size_type
FROM user_settings;
| username | prefs_type | theme_type | notif_type | font_size_type |
|---|---|---|---|---|
| alice | object | string | object | number |
Possible return values: object, array, string, number, boolean, null.
Combining Functions for Deep Analysis
Exploring All Nested Keys
You can combine jsonb_each with lateral joins to explore nested structures:
-- Get all top-level keys and the type of each value
SELECT
username,
kv.key AS setting_name,
jsonb_typeof(kv.value) AS value_type,
CASE
WHEN jsonb_typeof(kv.value) = 'object'
THEN (SELECT count(*) FROM jsonb_object_keys(kv.value))
ELSE 0
END AS nested_key_count
FROM user_settings,
jsonb_each(preferences) AS kv;
Flattening Nested JSON
Turn the notification settings into columns:
SELECT
username,
preferences ->> 'theme' AS theme,
preferences -> 'notifications' ->> 'email' AS email_notif,
preferences -> 'notifications' ->> 'push' AS push_notif,
preferences -> 'editor' ->> 'font_size' AS font_size,
preferences -> 'editor' ->> 'tab_size' AS tab_size
FROM user_settings;
This transforms nested JSON into a traditional tabular format, which is useful for reporting.
Searching Across All Nested Values
-- Find any user who has a 'true' value anywhere in their notification settings
SELECT DISTINCT username
FROM user_settings,
jsonb_each_text(preferences -> 'notifications') AS kv
WHERE kv.value = 'true';
jsonb_to_record() -- JSON to Row Type
Convert a JSONB object directly into a row with typed columns:
SELECT
username,
prefs.*
FROM user_settings,
jsonb_to_record(preferences) AS prefs(
theme text,
notifications jsonb
);
This is powerful when you want to project specific keys from a JSONB column into proper typed columns.
Try It Yourself
-
Use
jsonb_extract_path_textto get thefont_sizefrom theeditorobject insidepreferencesfor all users. -
Use
jsonb_object_keys()to list all top-level keys in thepreferencescolumn. How many unique keys are there across all users? -
Use
jsonb_each()to expand all preference key-value pairs for user 'alice'. What types of values does she have? -
Use
jsonb_typeof()to check the types of the top-level values in preferences. Which keys have object values vs simple scalar values?