Postgres jsonb: querying multi level - postgresql

In a Postgres (9+) table there is a column of type jsonb with the following json:
{
"dynamicFields":[
{
"name":"040",
"subfields":[
{
"name":"a",
"value":"abc"
},
{
"name":"a",
"value":"xyz"
}
]
}
]
}
I would like to write a query that return only the rows where the field name equals 040 and subfield a equals xyz.
This is as far as I got, so far:
select e.obj from my_table
cross join lateral jsonb_array_elements(my_column-> 'dynamicFields') as e(obj)
where e.obj ->> 'name' = '040' and e.obj ->> 'subfields' #> '{"name": "a", "value": "xyz"}'::jsonb
How should this query be to achieve this?

e.obj ->> 'subfields' has a text result. You'll want to use e.obj -> 'subfields' that returns the jsonb value where the #> operator works. Also the containment checks needs to have another array as the right hand side, so that it will test whether all values in the right array are contained in the left array - it doesn't work to pass the element object directly.
select e.obj from my_table
cross join lateral jsonb_array_elements(my_column-> 'dynamicFields') as e(obj)
where e.obj ->> 'name' = '040' and e.obj -> 'subfields' #> '[{"name": "a", "value": "xyz"}]'::jsonb
-- ^ ^ ^
(online demo)

As you have an equality condition you can use the "contains" operator directly by providing a JSON value of what you want. There is no need to unnest the arrays.
select *
from my_table
where my_column -> 'dynamicFields' #> '[{"name": "040", "subfields": [{"name":"a", "value": "xyz"}]}]'
Starting with Postgres 12 an alternative is to a SQL/JSON path operator:
select *
from my_table
where my_column #? '$.dynamicFields[*] ? (#.name == "040").subfields[*] ? (#.name == "a" && #.value == "xyz")'

Related

How to query json objects using Postgres JSONB column

I have JSON stored in a jsonb column:
{
"processedResult": {
"orderPayment": {
"paymentType": "VISA"
},
"store": "US"
}
}
What I have tried:
SELECT DISTINCT myData -> 'processedResult' -> 'orderPayment' -> 'paymentType'
FROM mytable
WHERE myData ->> 'processedResult' ->> 'store' = 'US'
The WHERE clause seems to be incorrect.
Desired Output:
VISA
Mastedcard
Postgres Version: PostgreSQL 11.13
You'll want to use
SELECT DISTINCT myData -> 'processedResult' -> 'orderPayment' ->> 'paymentType'
FROM mytable
WHERE myData -> 'processedResult' ->> 'store' = 'US'
Notice that -> returns the selected jsonb value, whereas ->> always returns a postgres text value (or NULL, or an error).

Different path formats for PostgreSQL JSONB functions

I'm confused by how path uses different formats depending on the function in the PostgreSQL JSONB documentation.
If I had a PostgreSQL table foo that looks like
pk
json_obj
0
{"values": [{"id": "a_b", "value": 5}, {"id": "c_d", "value": 6]}
1
{"values": [{"id": "c_d", "value": 7}, {"id": "e_f", "value": 8]}
Why does this query give me these results?
SELECT json_obj, -- {"values": [{"id": "a_b", "value": 5}, {"id": "c_d", "value": 6]}
json_obj #? '$.values[*].id', -- true
json_obj #> '$.values[*].id', -- ERROR: malformed array literal
json_obj #> '{values, 0, id}', -- "a_b"
JSONB_SET(json_obj, '$.annotations[*].id', '"hi"') -- ERROR: malformed array literal
FROM foo;
Specifically, why does #? support $.values[*].id (described on that page in another section) but JSONB_SET uses some other path format {bar,3,baz}?
Ultimately, what I would like to do and don't know how, is to remove non-alphanumeric characters (e.g. underscores in this example) in all id values represented by the path $.values[*].id.
The reason is that the operators have different data types on the right hand side.
SELECT oprname, oprright::regtype
FROM pg_operator
WHERE oprleft = 'jsonb'::regtype
AND oprname IN ('#?', '#>');
oprname | oprright
---------+----------
#> | text[]
#? | jsonpath
(2 rows)
Similarly, the second argument of jsonb_set is a text[].
Now '$.values[*].id' is a valid jsonpath, but not a valid text[] literal.
Thanks for the answers and comments about why the data types were different.
I wanted to post how I solved my problem:
Ultimately, what I would like to do and don't know how, is to remove
non-alphanumeric characters (e.g. underscores in this example) in all
id values represented by the path $.values[*].id.
WITH unnested AS (
SELECT f.pk, JSONB_ARRAY_ELEMENTS(f.json_obj -> 'values') AS value
FROM foo f
),
updated_values AS (
SELECT un.pk, JSONB_SET(un.value, '{id}', TO_JSONB(LOWER(REGEXP_REPLACE(un.value ->> 'id', '[^a-zA-Z0-9]', '', 'g'))), FALSE) AS new_value
FROM unnested un
WHERE value -> 'id' IS NOT NULL -- Had some values that didn't have 'id' keys
)
UPDATE foo f2
SET json_obj = JSONB_SET(f2.json_obj, '{values}', (SELECT JSONB_AGG(uv.new_value) FROM updated_values uv WHERE uv.pk = f2.pk), FALSE)
WHERE JSONB_PATH_EXISTS(f2.json_obj, '$.values[*].id') -- Had some values that didn't have 'id' keys

Deep search within jsonb field PostgreSQL without knowking the key

Assuming the following jsonb data :
{
"3f210b": {
"hash": "e60ab24c549ac7060fzedfefe563e8493d759bb"
},
"Siaa1b3": {
"hash": "d24b37efgregr1a2c6db3b9334b3bf4fef3f22bfc9a43f"
}
}
Is it possible, in PostgreSQL, to do a deep jsonb search without know the key (3f210b & Siaa1b3) but we know the hash.
With Postgres 11 you will need to unnest that value:
select *
from the_table t
where exists (select *
from jsonb_each(t.the_column) as x(ky, item)
where x.item ->> 'hash' = 'd24b37efgregr1a2c6db3b9334b3bf4fef3f22bfc9a43f');
With Postgres 12, you could use the new JSON/Path expression:
select *
from the_table t
where jsonb_path_exists(the_column, '$.*.hash ? (# == "d24b37efgregr1a2c6db3b9334b3bf4fef3f22bfc9a43f")')

org.postgresql.util.PSQLException: ERROR: set-returning functions are not allowed in WHERE

I need a help for the below mentioned detail,
I am using Postgres + Spring-Data-JPA. Moreover, I have used the jsonb data type for storing data.
I am trying to execute a query, but it gives me the following error:
ERROR: set-returning functions are not allowed in WHERE
The cause here is that I have added a jsonb condition in the WHERE clause (kindly refer to the below query for more detail).
Query (I have renamed column name just because of hiding the actual column name):
select distinct
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'firstName' as firstName,
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'lastName' as lastName,
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'country' as country
from
tale1 table10_
left outer join
table2 table21_
on table10_.end_user_id=table21_.end_user_id
left outer join
table3 table32_
on table10_.manufacturer_id=table32_.manufacturer_id
where
table21_.end_user_uuid=(
?
)
and table21_.is_active=true
and table32_.manufacturer_uuid=(
?
)
and table32_.is_active=true
and table10_.is_active=true
and table32_.is_active=true
and jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'action' = ('PENDING')
order by
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'firstName',
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'lastName'
limit ?
The following line in the above query is causing the error:
and jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'action' = ('PENDING')
Can anyone please guide me about how do fetch data from the inner JSON? Especially in my case I have an inner List and a few elements inside.
I recommend a lateral join with jsonb_array_elements for cases like that. Here is an example:
CREATE TABLE tale1 (
id integer PRIMARY KEY,
initiated_referral_detail jsonb NOT NULL
);
INSERT INTO tale1 VALUES
(1, '{
"name": "one",
"listOfAttribue": [
{ "id": 1, "action": "DONE"},
{ "id": 2, "action": "PENDING" },
{ "id": 3, "action": "ACTIVE" }
]
}');
INSERT INTO tale1 VALUES
(2, '{
"name": "two",
"listOfAttribue": [
{ "id": 1, "action": "DONE"},
{ "id": 2, "action": "ACTIVE" }
]
}');
To find all ids where the associated JSON contains an array element with action = PENDING, you can query like this:
SELECT DISTINCT id
FROM tale1 CROSS JOIN LATERAL
jsonb_array_elements(initiated_referral_detail -> 'listOfAttribue') AS attr
WHERE attr ->> 'action' = 'PENDING';

Postgres extract value from jsonb array

I have a jsonb field with an array like this one below:
[
{
"type":"discount",
"title":"Discount 10%"
},
{
"file":"zx5rP8EoacyfhqGndcSOnP8VYtkr9Ya8Nvf7oYL98YDsM1CLMYIurYvfVUU4AGkzBsovwssT0bq.pdf",
"type":"menu",
"title":"Some menu title etc"
}
]
I want to get the file attribute in case there is a type=menu in the array.
What I managed to do is to know if there is one, but how can I eventually extract the file value?
case when offers #> '[{"type":"menu"}]' then true else false end
I don't want to do something like this below because the array may not contain a discount type.
offers->1->'file'
Use jsob_array_elements() and ->> operator (see JSON Functions and Operators.)
with a_table(json_col) as (
values (
'[
{
"type":"discount",
"title":"Discount 10%"
},
{
"file":"zx5rP8EoacyfhqGndcSOnP8VYtkr9Ya8Nvf7oYL98YDsM1CLMYIurYvfVUU4AGkzBsovwssT0bq.pdf",
"type":"menu",
"title":"Some menu title etc"
}
]'::jsonb)
)
select value->>'file' as filename
from a_table,
lateral jsonb_array_elements(json_col)
where value->>'type' = 'menu'
filename
---------------------------------------------------------------------------------
zx5rP8EoacyfhqGndcSOnP8VYtkr9Ya8Nvf7oYL98YDsM1CLMYIurYvfVUU4AGkzBsovwssT0bq.pdf
(1 row)
Eg:
t=# with a as (with v as (select '[
{
"type":"discount",
"title":"Discount 10%"
},
{
"file":"zx5rP8EoacyfhqGndcSOnP8VYtkr9Ya8Nvf7oYL98YDsM1CLMYIurYvfVUU4AGkzBsovwssT0bq.pdf",
"type":"menu",
"title":"Some menu title etc"
}
]'::jsonb j)
select jsonb_array_elements(j) r from v) select r->>'file' from a where r->>'type' = 'menu';
?column?
---------------------------------------------------------------------------------
zx5rP8EoacyfhqGndcSOnP8VYtkr9Ya8Nvf7oYL98YDsM1CLMYIurYvfVUU4AGkzBsovwssT0bq.pdf
(1 row)