How can I conditionally add elements to a jsonb array? [duplicate] - postgresql

This question already has answers here:
PostgreSQL JSON building an array without null values
(4 answers)
Closed 8 months ago.
Is there a way in which I can conditionally add elements into a postgres jsonb array? I'm trying to construct an array to be added into a larger object where most of the elements are always required but I'd like to have some of them optional.
As a simplified example:
select jsonb_build_array(
jsonb_build_object('a', a),
jsonb_build_object('b', b),
jsonb_build_object('c', c),
case when a + b <> c then
jsonb_build_object('error', c - (a + b))
end
) from ( values (2, 2, 5) ) as things (a,b,c);
This works fine when a+b<>c but when a+b=c i get a null in the array.e.g.
sophia=> \i ~/cc/dpdb/migration/foo.sql
jsonb_build_array
----------------------------------------------
[{"a": 2}, {"b": 2}, {"c": 5}, {"error": 1}]
(1 row)
sophia=> \i ~/cc/dpdb/migration/foo.sql
jsonb_build_array
--------------------------------------
[{"a": 2}, {"b": 2}, {"c": 4}, null]
(1 row)
sophia=>
Is there a way to add the element without the null or if added, remove the null? Obviously, I could put the whole block in a case and duplicate the first few lines but that would be rather ugly and verbose. There's jsonb_strip_nulls but that only works on objects not arrays.

You have to use a second step because you cannot create "no element" in your syntax. Either you really separate both cases with two different array creations or you have to conditionally adjust the created array afterwards:
demo:db<>fiddle
SELECT
CASE WHEN a + b <> c THEN
my_array || jsonb_build_object('error', c - (a + b))
ELSE
my_array
END
FROM (
select
a, b, c,
jsonb_build_array(
jsonb_build_object('a', a),
jsonb_build_object('b', b),
jsonb_build_object('c', c)
) AS my_array
from ( values (2, 2, 5), (2, 2, 4) ) as things (a,b,c)
) s

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;

Casting string like "[1, 2, 3]" to array [duplicate]

This question already has an answer here:
Handle string to array conversion in pyspark dataframe
(1 answer)
Closed 4 years ago.
Pretty straightforward. I have an array-like column encoded as a string (varchar) and want to cast it to array (so I can then explode it and manipulate the elements in "long" format).
The two most natural approaches don't seem to work:
-- just returns a length-1 array with a single string element '[1, 2, 3]'
select array('[1, 2, 3]')
-- errors: DataType array is not supported.
select cast('[1, 2, 3]' as array)
The ugly/inelegant/circuitous way to get what I want is:
select explode(split(replace(replace('[1, 2, 3]', '['), ']'), ', '))
-- '1'
-- '2'
-- '3'
(regexp_replace could subsume the two replace but regex with square brackets are always a pain; ltrim and rtrim or trim(BOTH '[]'...) could also be used)
Is there any more concise way to go about this? I'm on Spark 2.3.1.
I am assuming here that the elements are digits. But you get the idea
>>> s = '[1,2,3]'
>>> list(c for c in s if c.isdigit())
['1', '2', '3']
>>> map(int, list(c for c in s if c.isdigit()))
[1, 2, 3]

Compare array against array column in postgres

# things :string is an Array
scope :things, ->(q) { where('ARRAY[?]::varchar[] IN things', Array.wrap(q)) }
scope :things, ->(q) { where('things && ARRAY[?]::varchar[]', Array.wrap(q)) }
scope :things, ->(q) { where('ARRAY[?]::varchar[] <# things', Array.wrap(q)) }
I've tried a few versions, but I can't seem to find the proper incantation. I'm looking to find any row that has any of the things in the array... is there any overlap?
[1, 2, 3] & [1, 8] = t
[1, 2, 3] & [8, 9] = f
I'm trying to mimic ActiveRecord's default where behavior. If I give it an array, it'll get all the matching rows. Is this possible with postgres arrays? Is it even efficient?
One way of doing this is by converting the arrays to a set of rows. Once you have the arrays as set of rows, you can do an intersection between them and check if the result is empty set.
For example:
CREATE TABLE my_test_table(id BIGINT, test_array BIGINT[]);
INSERT INTO my_test_table(id, test_array)
VALUES
(1, array[1,2,3]),
(2, ARRAY[1,5,8]);
SELECT * FROM my_test_table
WHERE array_length((SELECT array
(
SELECT UNNEST(test_array)
INTERSECT
SELECT UNNEST(array[3,15,2])
)), 1) > 0;
The result of the SELECT statement above is:
1 | {1,2,3}
This allows for more complex matching of elements of 2 arrays. For example, if you would like to select the arrays that have at least 2 common elements, you could just change the WHERE part to
WHERE array_length((SELECT array
(
SELECT UNNEST(test_array)
INTERSECT
SELECT UNNEST(array[3,15,2])
)), 1) > 1;

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

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'

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