How to get only the jsonb of specific keys from postgres? - postgresql

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'

Related

How to merge a JSONB array of objects into a single object in PostgreSQL

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;

How to update a table without creating new rows

Imagine I have a set of key-value data from a primary key to values:
id
foo
1
abc
2
def
3
ghj
... that need to be updated in a table.
And I want to update all of these in one query. Naturally, upserts come to mind, which works quite well:
INSERT INTO my_table (id, foo) VALUES ((1, 'abc'), (2, 'def'), (3, 'ghj'))
ON CONFLICT (id) DO UPDATE SET foo = excluded.foo;
This works fine, but what if I don't actually want to insert the row with id=3 when it doesn't already exist in the table my_table?
I don't see why you would need an INSERT at all if you just want to UPDATE the rows?
update my_table
set foo = v.foo
from (
VALUES (1, 'abc'), (2, 'def'), (3, 'ghj')
) as v(id, foo)
where v.id = my_table.id;
One thing I have already tried (and it works) is to use a source query which receives all of the source data as a json list and then inner joins to the existing table to throw away all the records that don't have an entry in my_table:
[
{"id": 1, "foo": "abc"},
{"id": 2, "foo": "def"},
{"id": 3, "foo": "ghj"}
]
which is passed as the only parameter to this query:
WITH source AS (
SELECT my_table.id, x.foo FROM jsonb_to_recordset($1::jsonb) AS x(id int, foo text)
JOIN my_table ON x.id = my_table.id
)
INSERT INTO my_table (id, foo)
(SELECT * FROM source)
ON CONFLICT(id) DO UPDATE SET foo = excluded.foo

PostgreSQL jsonb - omit multiple nested keys

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.

How can I merge an existing jsonb field when upserting a record in Postgres?

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

How to get the SUM of values on a JSONB column

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);