The task is to remove multiple nested keys from jsonb field.
Is there any way to shorten this expression without writing a custom function?
SELECT jsonb '{"a": {"b":1, "c": 2, "d": 3}}' #- '{a,b}' #- '{a,d}';
suppose we need to delete more than 2 keys
There is no way to shorten the expression. If your goal is to pass to the query a single array of keys to be deleted you can use jsonb_set() with jsonb_each():
with my_table(json_col) as (
values
(jsonb '{"a": {"b":1, "c": 2, "d": 3}}')
)
select jsonb_set(json_col, '{a}', jsonb_object_agg(key, value))
from my_table
cross join jsonb_each(json_col->'a')
where key <> all('{b, d}') -- input
group by json_col -- use PK here if exists
jsonb_set
-----------------
{"a": {"c": 2}}
(1 row)
The solution is obviously more expensive but may be handy when dealing with many keys to be deleted.
NVM, figured it out)
For this particular case, we can re-assign property with removed keys (flat):
SELECT jsonb_build_object('a', ('{ "b":1, "c": 2, "d": 3 }' - ARRAY['b','d']));
More general approach:
SELECT json_col || jsonb_build_object('<key>',
((json_col->'<key>') - ARRAY['key-1', 'key-2', 'key-n']));
Not very useful for deep paths, but works ok with 1-level nesting.
Related
I have a JSONB column where each row contains an array of multiple objects.
'[{"a": 1}, {"b": 2}, {"c": 0.5}]'::jsonb
I want to merge all of them together into a single object:
'{"a": 1, "b": 2, "c": 0.5}'::jsonb
I have tried using coalesce to merge them without success. I've also been playing around with multiple other inbuilt functions from Postgres but can't seem to figure this one out.
Any ideas?
You need to unnest the array and then aggregate the key/values back into a single object:
select (select jsonb_object_agg(e.ky, e.val)
from jsonb_array_elements(t.the_column) as x(element)
cross join jsonb_each(x.element) as e(ky,val))
from the_table t;
Note, that if the array contains duplicate keys, the "last one" will win in this case (because JSON doesn't allow duplicate keys)
With Postgres 12 or later this can be simplified a little bit:
select (select jsonb_object_agg(e.item ->> 'key', e.item -> 'value')
from jsonb_path_query(t.the_column, '$[*].keyvalue()') as e(item))
from the_table t
CTE solution:
WITH cte (
each_json
) AS (
SELECT
jsonb_path_query('[{"a": 1}, {"b": 2}, {"c": 0.5}]'::jsonb, '$[*]')
),
cte1 AS (
SELECT
(jsonb_each(each_json)).*
FROM
cte
)
SELECT
jsonb_object_agg(key, value)
FROM
cte1;
I have a doubt with modification of jsonb data type in postgres
Basic setup:-
array=> ["1", "2", "3"]
and now I have a postgresql database with an id column and a jsonb datatype column named lets just say cards.
id cards
-----+---------
1 {"1": 3, "4": 2}
thats the data in the table named test
Question:
How do I convert the cards of id->1 FROM {"1": 3, "4": 2} TO {"1": 4, "4":2, "2": 1, "3": 1}
How I expect the changes to occur:
From the array, increment by 1 all elements present inside the array that exist in the cards jsonb as a key thus changing {"1": 3} to {"1": 4} and insert the values that don't exist as a key in the cards jsonb with a value of 1 thus changing {"1":4, "4":2} to {"1":4, "4":2, "2":1, "3":1}
purely through postgres.
Partial Solution
I asked a senior for support regarding my question and I was told this:-
Roughly (names may differ): object keys to explode cards, array_elements to explode the array, left join them, do the calculation, re-aggregate the object. There may be a more direct way to do this but the above brute-force approach will work.
So I tried to follow through it using these two functions json_each_text(), json_array_elements_text() but ended up stuck halfway into this as well as I was unable to understand what they meant by left joining two columns:-
SELECT jsonb_each_text(tester_cards) AS each_text, jsonb_array_elements_text('[["1", 1], ["2", 1], ["3", 1]]') AS array_elements FROM tester WHERE id=1;
TLDR;
Update statement that checks whether a range of keys from an array exist or not in the jsonb data and automatically increments by 1 or inserts respectively the keys into the jsonb with a value of 1
Now it might look like I'm asking to be spoonfed but I really haven't managed to find anyway to solve it so any assistance would be highly appreciated 🙇
The key insight is that with jsonb_each and jsonb_object_agg you can round-trip a JSON object in a subquery:
SELECT id, (
SELECT jsonb_object_agg(key, value)
FROM jsonb_each(cards)
) AS result
FROM test;
(online demo)
Now you can JOIN these key-value pairs against the jsonb_array_elements of your array input. Your colleague was close, but not quite right: it requires a full outer join, not just a left (or right) join to get all the desired object keys for your output, unless one of your inputs is a subset of the other.
SELECT id, (
SELECT jsonb_object_agg(COALESCE(obj_key, arr_value), …)
FROM jsonb_array_elements_text('["1", "2", "3"]') AS arr(arr_value)
FULL OUTER JOIN jsonb_each(cards) AS obj(obj_key, obj_value) ON obj_key = arr_value
) AS result
FROM test;
(online demo)
Now what's left is only the actual calculation and the conversion to an UPDATE statement:
UPDATE test
SET cards = (
SELECT jsonb_object_agg(
COALESCE(key, arr_value),
COALESCE(obj_value::int, 0) + (arr_value IS NOT NULL)::int
)
FROM jsonb_array_elements_text('["1", "2", "3"]') AS arr(arr_value)
FULL OUTER JOIN jsonb_each_text(cards) AS obj(key, obj_value) ON key = arr_value
);
(online demo)
I'm aware that you can remove keys from a jsonb in postgres using something like this
select '{"a": 1, "b": 2, "c":3}'::jsonb -'a';
?column?
----------
{"b": 2 "c":3}
(1 row)
Is there a way to only grab specific keys? Like let's say I just want to get the key-value pair of just the 'a' key.
Something like this?
select '{"a": 1, "b": 2}'::jsonb + 'a' + 'b';
?column?
----------
{"a": 1, "b": 2}
(1 row)
EDIT: Changed the example to to show that I'd like to grab multiple keys-value pairs from the jsonb and not just one pair.
You can filter down to a single key fairly easily like so:
jsonb_object(ARRAY[key, jsonb_data -> key])
...or you can filter down to multiple keys:
(SELECT jsonb_object_agg(key, value) FROM jsonb_each(jsonb_data) WHERE key IN ('a', 'b'))
Or on a more complex condition, if you want:
(
SELECT jsonb_object_agg(key, value)
FROM jsonb_each(jsonb_data)
WHERE
key NOT LIKE '__%'
AND jsonb_typeof(value) != 'null'
)
These kinds of questions can be answered very easily by simply reading the documentation.
I actually found that this way works to.
select jsonb_build_object('key', column->'key') from table;
reference:
https://www.reddit.com/r/PostgreSQL/comments/73auce/new_user_to_postgres_can_i_grab_multiple_keys_of/
You can do this
SELECT jsonb_column->>'key_name_here' as 'alias_name_as_you_like' from table_name
In the case of the query asked above, it would be
select '{"a": 1, "b": 2, "c":3}'::jsonb->>'a'
You can get just the value like so:
select '{"a": 1, "b": 2}'::jsonb-> 'a';
If you must, you can transform that back into jsonb manually, or perhaps go through an array, hstore or other intermediate type. Here's the "manual" way
select ('{ "a": '||('{"a": 1, "b": 2}'::jsonb->'a')::text||'}')::jsonb
Paraphrasing the situation
we have a jsonb value and multiple keys in mind, a and c
select '{"a": 1, "b": 2, "c":3}'::jsonb - '{a,c}'::text[];
- is a tidy operator but gives us the opposite of what you want
{"b": 2}
solution is to wrap that in array(select jsonb_object_keys(...)) and perform the - again
select '{"a": 1, "b": 2, "c":3}'::jsonb - array(select jsonb_object_keys('{"a": 1, "b": 2, "c":3}'::jsonb - '{a,c}'::text[]));
you get a json with only those keys, a and c
{"a": 1, "c": 3}
If you want to filter multiple rows with JSONB documents in each of them:
-- Let's generate rows with JSONB column:
WITH s AS (SELECT generate_series(1, 100) num),
g AS (SELECT num, jsonb_build_object('a', s.num, 'b', s.num * 2) obj FROM s),
-- A "filter" adding (in my example only keys of "filter" document remain in result rows)
j AS (SELECT '{"a": "int", "c": "string"}'::jsonb AS filter),
a AS (SELECT (ARRAY(SELECT jsonb_object_keys(filter))) AS ar FROM j),
-- Useless keys removing
o AS (SELECT jsonb_object_agg(l.key, l.value) obj
FROM g, LATERAL jsonb_each(g.obj) l, a
WHERE l.key = ANY(a.ar)
GROUP BY num)
SELECT * FROM o ORDER BY obj->'a';
Begin;
CREATE TEMPORARY TABLE test (id serial, jdoc jsonb);
insert into test(jdoc) values('{"a": {"b":"foo"}}');
insert into test(jdoc) values('{"a": "test"}');
insert into test(jdoc) values('{"a":[2,3,4]}');
insert into test(jdoc) values('{"b":[2,3,4]}');
commit;
select (jdoc->'a') from test where jdoc ? 'a'
will get all the specific key's value.
If you want JSONB of the specific key: select jdoc from test where jdoc ? 'a'
Say I have a table on Postgres with a jsonb column containing {"a": 1, "b": 2}. Now I'd like to upsert a record with the same id and {"b": 10, "c": 20} as the jsonb column value.
Consequently, I'd like the jsonb field of the row to contain {"a": 1, "b": 10, "c": 20}. How can this be achieved?
If you want an "upsert", you can do this with insert ... on conflict...
insert into the_table (id, json_column)
values (1, '{"b": 10, "c": 20}'::jsonb)
on conflict (id) do update
set json_column = table_name.json_column || excluded.json_column;
If concatenate 2 jsonb value, you achieve what you want, for example:
select '{"a": 1, "b": 2}'::jsonb || '{"b": 10, "c": 20}'::jsonb
produces: "{"a": 1, "b": 10, "c": 20}"
if both operands are objects with a common key field name, the value of the field in the result will just be the value from the right hand operand.
https://www.postgresql.org/docs/current/static/functions-json.html
Well, my example is not about merging 2 json fields, but arrays instead:
insert into tag_lists (article_id, tags) values (1, '{job}')
on conflict (article_id)
do update set tags = (
select array_agg(distinct x) from unnest(tag_lists.tags || excluded.tags) x
);
Thanks to this answer for providing comprehensive snippets
I've got a 1-dimension JSONB on postgresql like this:
SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb;
How to get the SUM of values on a JSONB column?
Like the sum of 1+2+3?
PostgreSQL has the jsonb_object_keys function, but I was looking for something like "jsonb_object_values" (I know that this function does not exists)
# select jsonb_object_keys( '{"a": 1, "b": 2, "c": 3}'::jsonb );
jsonb_object_keys
-------------------
a
b
c
(3 rows)
The jsonb_each_text() function expands a set of JSON objects into rows in (key, value) format. Since it returns a set of rows, you should use it as a row source. Since it returns data in the text format, you should cast it to the appropriate type before further processing.
SELECT sum(v::integer)
FROM jsonb_each_text('{"a": 1, "b": 2, "c": 3}'::jsonb) j(k,v);