Deep search within jsonb field PostgreSQL without knowking the key - postgresql

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")')

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

UPDATE SET with different value for each row

I have python dict with relationship between elements and their values. For example:
db_rows_values = {
<element_uuid_1>: 12,
<element_uuid_2>: "abc",
<element_uuid_3>: [123, 124, 125],
}
And I need to update it in one query. I made it in python through the query generation loop with CASE:
sql_query_elements_values_part = " ".join([f"WHEN '{element_row['element_id']}' "
f"THEN '{ujson.dumps(element_row['value'])}'::JSONB "
for element_row in db_row_values])
query_part_elements_values_update = f"""
elements_value_update AS (
UPDATE m2m_entries_n_elements
SET value =
CASE element_id
{sql_query_elements_values_part}
ELSE NULL
END
WHERE element_id = ANY(%(elements_ids)s::UUID[])
AND entry_id = ANY(%(entries_ids)s::UUID[])
RETURNING element_id, entry_id, value
),
But now I need to rewrite it in plpgsql. I can pass db_rows_values as array of ROWTYPE or as json but how can I make something like WHEN THEN part?
Ok, I can pass dict as JSON, convert it to rows with json_to_recordset and change WHEN THEN to SET value = (SELECT.. WHERE)
WITH input_rows AS (
SELECT *
FROM json_to_recordset(
'[
{"element_id": 2, "value":"new_value_1"},
{"element_id": 4, "value": "new_value_2"}
]'
) AS x("element_id" int, "value" text)
)
UPDATE table1
SET value = (SELECT value FROM input_rows WHERE input_rows.element_id = table1.element_id)
WHERE element_id IN (SELECT element_id FROM input_rows);
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=f8b6cd8285ec7757e0d8f38a1becb960

Convert individual postgres jsonb array elements to row elements

I have to query a table with 2 columns, id and content. Id is just a uuid and the content column looks like
{
"fields": [
{
"001": "mig00004139229"
},
{
"856": {
"ind1": " ",
"ind2": " ",
"subfields": [
{
"u": "https://some.domain.com"
},
{
"z": "some text"
}
]
}
},
{
"999": {
"subfields": [
{
"i": "81be1acf-11df-4d13-a5c6-4838e3a808ee"
},
{
"s": "3a6aa357-8fd6-4451-aedc-13453c1f2296"
}
]
}
}
]
}
I need to select the id, 001, and 856 elements where the subfield "u" domain matches a string "domain.com" so the output would be
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
mig00004139229
https://some.domain.com
If this were a flat table, the query would correspond with "select id, 001, 856 from table where 856 like '%domain.com%'"
I can select individual columns based on the criteria I need, but they appear in separate rows except the id which appears with any other individual field in a regular select statement. How would I get the other fields to appear in the same row since it's part of the same record?
Unfortunately, my postgres version doesn't support jsonb_path_query, so I've been trying something along the lines of:
SELECT id, jsonb_array_elements(content -> 'fields') -> '001',
jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields'
FROM
mytable
WHERE....
This method returns the data I need, but the individual elements arrive on separate rows with the with the id in the first column and nulls for every element that is neither the 001 nor 856 e.g.
id
001
856
id_for_first_record
001_first_record
null
id_for_first_record
null
null
id_for_first_record
null
null
id_for_first_record
null
856_first_record
id_for_second_record
001_second_record
null
id_for_second_record
null
null
id_for_second_record
null
null
id_for_second_record
null
856_second_record
Usable, but clunky so I'm looking for something better
I think my query can help you. There are different ways to resolve this, I am not sure if this is the best approach.
I use jsonb_path_query() function with the path for the specified JSON value.
SELECT
id,
jsonb_path_query(content, '$.fields[*]."001"') AS "001",
jsonb_path_query(content, '$.fields[*]."856".subfields[*].u') AS "856"
FROM t
WHERE jsonb_path_query_first(content, '$.fields[*]."856".subfields[*].u' )::text ilike '%domain%';
Output:
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
"mig00004139229"
"https://some.domain.com"
UPDATED: because of Postgresql version is prior to 12.
You could try something like this, but I think there must be a better approach:
SELECT
t.id,
max(sq1."001") AS "001",
max(sq2."856") AS "856"
FROM t
INNER JOIN (SELECT id, (jsonb_array_elements(content -> 'fields') -> '001')::text AS "001" FROM t) AS sq1 ON t.id = sq1.id
INNER JOIN (SELECT id, (jsonb_array_elements(jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields') -> 'u')::text AS "856" FROM t) AS sq2 ON t.id = sq2.id
WHERE sq2."856" ilike '%domain%'
GROUP BY t.id;

Postgres jsonb: querying multi level

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")'

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)