snippetsqlMinor
How to select sub-object with given keys from JSONB?
Viewed 0 times
fromjsonbwithsubkeyshowselectobjectgiven
Problem
How to extract / get / select a "sub object" from a json(b) object in Postgres?
There seems to be a lot of info that get me almost all the way there but not quite. Lots of stuff about converting to records, filtering and then building a new object out of it. TBH, I'm really surprised that this is not a builtin capability. Perhaps there's a simple way to achieve this by composing builtin fns? What I'm looking for is essentially the equivalent of
would return this:
Kind of like
There seems to be a lot of info that get me almost all the way there but not quite. Lots of stuff about converting to records, filtering and then building a new object out of it. TBH, I'm really surprised that this is not a builtin capability. Perhaps there's a simple way to achieve this by composing builtin fns? What I'm looking for is essentially the equivalent of
select-keys; a function (say, jsonb_select_keys) that given this:SELECT jsonb_select_keys('{"a":42,"b":43,"c":44,"d":97}', '{a,d,e}');would return this:
{"a":42,"d":97}Kind of like
jsonb_path_query_array but for k-v pairs instead of values.Solution
You can remove given keys with the
Deletes all matching keys or array elements from the left operand.
But I wouldn't know of a built-in function or operator doing what you ask (incl. the current Postgres 15).
Remarkably, it's the other way round with SQL
Also can't think of an easy way with the SQL/JSON path language (Postgres 12+).
Workaround for
You can create a simple function like:
db<>fiddle here
Call:
Returns:
You get NULL for NULL or empty input for either argument (as demonstrated in the fiddle). So I declared the function
Order of keys?
Because the
will preserve semantically-insignificant white space between tokens,
as well as the order of keys within JSON objects. Also, if a JSON
object within the value contains the same key more than once, all the
key/value pairs are kept. (The processing functions consider the last
value as the operative one.) By contrast,
white space, does not preserve the order of object keys, and does not
keep duplicate object keys. If duplicate keys are specified in the
input, only the last value is kept.
Meaning, if there can be duplicate keys in the input, the order of rows is significant. My function does not explicitly order rows, so the result would not strictly be
Again, this would prohibit function inlining - if it could be inlined to begin with.
Equivalent for
For completeness: the same for
The first variant does not try to preserve the given order of keys, and it eliminates possible duplicates in the input array of keys (but not possible duplicates from the
The second variant preserves the given order of keys and keeps all possible duplicates. (If the same key is 2x in the
About
I made the
You could use
- operator (the inverse of what you have in mind). The manual:jsonb - text[] → jsonbDeletes all matching keys or array elements from the left operand.
'{"a": "b", "c": "d"}'::jsonb - '{a,c}'::text[] → {}But I wouldn't know of a built-in function or operator doing what you ask (incl. the current Postgres 15).
Remarkably, it's the other way round with SQL
SELECT statements. There you get a positive list with SELECT a,d,e FROM tbl, but cannot simply get "all columns except [a,d,e]" like you can get "all keys except [a,d,e]" from a JSON object with the above operator. I would love to have the complementary feature for each.Also can't think of an easy way with the SQL/JSON path language (Postgres 12+).
Workaround for
jsonbYou can create a simple function like:
CREATE OR REPLACE FUNCTION f_jsonb_select_keys(_js jsonb, _keys text[])
RETURNS jsonb
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
$func$
SELECT jsonb_object_agg(t.key, t.value)
FROM jsonb_each(_js) t
WHERE t.key = ANY (_keys);
$func$;db<>fiddle here
Call:
SELECT f_jsonb_select_keys('{"a":42,"b":43,"c":44,"d":97}', '{a,d,e}');Returns:
{"a":42,"d":97}You get NULL for NULL or empty input for either argument (as demonstrated in the fiddle). So I declared the function
STRICT (a.k.a. RETURNS NULL ON NULL INPUT), even though the nested function jsonb_object_agg() is not STRICT. That could mess with function inlining, but since this one cannot be inlined anyway (containing an aggregate function), we might as well. See:- Function executes faster without STRICT modifier?
- Can declaring function volatility IMMUTABLE harm performance?
IMMUTABLE is only kind of true. Keep reading.Order of keys?
jsonb does not preserve the order of keys (keys are ordered in deterministic fashion internally), so we need not bother about the order of input rows in the aggregation. Except when there can be duplicates, then the latest copy from the input prevails. The manual:Because the
json type stores an exact copy of the input text, itwill preserve semantically-insignificant white space between tokens,
as well as the order of keys within JSON objects. Also, if a JSON
object within the value contains the same key more than once, all the
key/value pairs are kept. (The processing functions consider the last
value as the operative one.) By contrast,
jsonb does not preservewhite space, does not preserve the order of object keys, and does not
keep duplicate object keys. If duplicate keys are specified in the
input, only the last value is kept.
Meaning, if there can be duplicate keys in the input, the order of rows is significant. My function does not explicitly order rows, so the result would not strictly be
IMMUTABLE. But the input comes directly from jsonb_each() and there cannot be duplicate keys in jsonb (unlike json). Possible duplicates in the input array of keys are irrelevant for the = ANY construct. So duplicates can never happen. IMMUTABLE after all.Again, this would prohibit function inlining - if it could be inlined to begin with.
Equivalent for
jsonFor completeness: the same for
json instead of jsonb:The first variant does not try to preserve the given order of keys, and it eliminates possible duplicates in the input array of keys (but not possible duplicates from the
json input!):CREATE OR REPLACE FUNCTION f_json_select_keys(_js json, _keys text[])
RETURNS json
LANGUAGE sql STABLE STRICT PARALLEL SAFE AS
$func$
SELECT json_object_agg (t.key, t.value)
FROM json_each(_js) t
WHERE key = ANY (_keys);
$func$;The second variant preserves the given order of keys and keeps all possible duplicates. (If the same key is 2x in the
json input, and 3x in the input array of keys, you get it 6x in the result.):CREATE OR REPLACE FUNCTION f_json_select_keys(_js json, _keys text[])
RETURNS json
LANGUAGE sql STABLE STRICT PARALLEL SAFE AS
$func$
SELECT json_object_agg (t.key, t.value ORDER BY ord)
FROM unnest(_keys) WITH ORDINALITY k(key, ord)
JOIN json_each(_js) t USING (key);
$func$;About
WITH ORDINALITY:- How to preserve the original order of elements in an unnested array?
I made the
json functions STABLE because json_object_agg() is only STABLE as opposed to jsonb_object_agg(), which is IMMUTABLE.You could use
json_build_object ('a', js->'a', 'd', js->'d', 'e', js->'e') but that would include all keys, with a NULL value if not found. Not exactly your request. And you couldn't tell the difference between a missing key and the same key with an actual NULL value.Code Snippets
CREATE OR REPLACE FUNCTION f_jsonb_select_keys(_js jsonb, _keys text[])
RETURNS jsonb
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
$func$
SELECT jsonb_object_agg(t.key, t.value)
FROM jsonb_each(_js) t
WHERE t.key = ANY (_keys);
$func$;SELECT f_jsonb_select_keys('{"a":42,"b":43,"c":44,"d":97}', '{a,d,e}');{"a":42,"d":97}CREATE OR REPLACE FUNCTION f_json_select_keys(_js json, _keys text[])
RETURNS json
LANGUAGE sql STABLE STRICT PARALLEL SAFE AS
$func$
SELECT json_object_agg (t.key, t.value)
FROM json_each(_js) t
WHERE key = ANY (_keys);
$func$;CREATE OR REPLACE FUNCTION f_json_select_keys(_js json, _keys text[])
RETURNS json
LANGUAGE sql STABLE STRICT PARALLEL SAFE AS
$func$
SELECT json_object_agg (t.key, t.value ORDER BY ord)
FROM unnest(_keys) WITH ORDINALITY k(key, ord)
JOIN json_each(_js) t USING (key);
$func$;Context
StackExchange Database Administrators Q#290005, answer score: 8
Revisions (0)
No revisions yet.