Get nested objects values from JSON in Postgres - json

So here is my JSON column in my Postgres DB:
{
"objekt_art": {
"86": {
"code": "86",
"bezeichnung_de": "Kino",
"bezeichnung_fr": "Cinéma",
"bezeichnung_it": "Cinema",
"bezeichnung_en": null,
"kurz_bezeichnung_de": "Kino",
"relevant_fuer_berechnung_steuerquote": true
},
"27": {
"code": "27",
"bezeichnung_de": "Kiosk",
"bezeichnung_fr": "Kiosque",
"bezeichnung_it": "Chiosco",
"bezeichnung_en": null,
"kurz_bezeichnung_de": "Kiosk",
"relevant_fuer_berechnung_steuerquote": true
}
}
}
I need to be able to query the bezechnung_de for example where code = 86.
The number of code i can pass from another table.
How can i for example make a query with two columns. One with the number and the second with bezeichnung_de.
Like this:
Code Bez
86 Kino

Sample data structure and sample table for join data: dbfiddle
select
je.value -> 'code' as "Code",
je.value -> 'bezeichnung_de' as "Bez"
from
test t
cross join jsonb_each((data::jsonb ->> 'objekt_art')::jsonb) je
-- In table test_join I insert value 86 for join record
inner join test_join tj on je.key::int = tj.json_id

As you know the code, this is fairly easy:
select t.the_column -> 'objekt_art' -> '86' ->> 'code' as code,
t.the_column -> 'objekt_art' -> '86' ->> 'bezeichnung_de' as bez
from the_table t
where ...
The value '86' can be a parameter. The first expression to select the code isn't really needed though, as you could replace it with the constant value (=parameter) directly.
If the "outer" JSON key isn't the same value as the code value, you could use something like this:
select o.value ->> 'code' as code,
o.value ->> 'bezeichnung_de' as bez
from the_table t
cross join jsonb_each(t.the_column -> 'objekt_art') o(key, value)
where o.key = '86'
and ... other conditions ...
If you are using Postgres 13 or later, this can also be written as a JSON path expression:
select a.item ->> 'code' as code,
a.item ->> 'bezeichnung_de' as bez
from (
select jsonb_path_query_first(t.the_column, '$.objekt_art.* ? (#.code == "86")') as item
from the_table t
where ....
) a
All examples assume that the column is defined with the data jsonb which it should be. If it's not you need to cast it: the_column::jsonb

Related

Update value inside of nested json array

I have JSON stored in a table. The JSON is nested and has the following structure
[
{
"name": "abc",
"ques": [
{
"qId": 100
},
{
"qId": 200
}
]
},{
"name": "xyz",
"ques": [
{
"qId": 100
},
{
"qId": 300
}
]
}
]
Update TABLE_NAME
set COLUMN_NAME = jsonb_set(COLUMN_NAME, '{ques,qId}', '101')
WHERE COLUMN_NAME->>'qId'=100
I am trying to update qId value from JSON. If qId is 100, I want to update it to 101.
1st solution, simple but to be used carefully
You convert your json data into text and you use the replace function :
Update TABLE_NAME
set COLUMN_NAME = replace(COLUMN_NAME :: text,'"qId": 100}', '"qId": 101}') :: jsonb
2nd solution more elegant and more complex
jsonb_set cannot make several replacements in the same jsonb data at the same time. To do so, you need to create your own aggregate based on the jsonb_set function :
CREATE OR REPLACE FUNCTION jsonb_set(x jsonb, y jsonb, path text[], new_value jsonb) RETURNS jsonb LANGUAGE sql AS $$
SELECT jsonb_set(COALESCE(x, y), path, new_value) ; $$ ;
CREATE OR REPLACE AGGREGATE jsonb_set_agg(x jsonb, path text[], new_value jsonb)
( stype = jsonb, sfunc = jsonb_set);
Then you get your result with the following query :
UPDATE TABLE_NAME
SET COLUMN_NAME =
( SELECT jsonb_set_agg(COLUMN_NAME :: jsonb, array[(a.id - 1) :: text, 'ques', (b.id - 1) :: text], jsonb_build_object('qId', 101))
FROM jsonb_path_query(COLUMN_NAME :: jsonb, '$[*]') WITH ORDINALITY AS a(content, id)
CROSS JOIN LATERAL jsonb_path_query(a.content->'ques', '$[*]') WITH ORDINALITY AS b(content, id)
WHERE (b.content)->'qId' = to_jsonb(100)
)
Note that this query is not universal, and it must breakdown the jsonb data according to its structure.
Note that jsonb_array_elements can be used in place of jsonb_path_query, but you will get an error with jsonb_array_elements when the jsonb data is not an array, whereas you won't get any error with jsonb_path_query in lax mode which is the default mode.
Full test results in dbfiddle
You must specify the whole path to the value.
In this case your json is an array so you need to address which element of this array your are trying to modify.
A direct approach (over your example) would be:
jsonb_set(
jsonb_set(
COLUMN_NAME
, '{0,ques,qId}'
, '101'
)
, '{1,ques,qId}'
, '101'
)
Of course, if you want to modify every element of different arrays of different lengths you would need to elaborate this approach disassembling the array to modify every contained element.

Get element value in a mysql nested json data

What would be the right way of getting Ajax, i.e. the value for the last occurence for key child1Dob1, from a json field that has a data structure that looks like the below,
{
"data": {
"data": {
"data": {
"child1Dob1": "Andy"
},
"child1Dob1": "Bob"
},
"child1Dob1": "Rick"
},
"child1Dob1": "Ajax"
}
The below query was an attempt from a similar question but i am getting a null value, so obviously i am missing something.
SELECT JSON_EXTRACT(`containerValue`,CONCAT("$.data[",JSON_LENGTH(`containerValue` ->> '$.data')-1,"]")) from myTable where containerKey = 'theContainer';
For CREATE TABLE test (data JSON):
WITH RECURSIVE
cte AS (
SELECT data, data -> '$.data' subdata
FROM test
UNION ALL
SELECT subdata, subdata -> '$.data'
FROM cte
WHERE subdata IS NOT NULL
)
SELECT data ->> '$.child1Dob1'
FROM cte
WHERE subdata IS NULL;

postgres select from json field as column for each key

I have Items (id, data (json)) table. Data column structure is this:
"somethingNotImportant":{
"someName":"Its a name",
"someProduct":"its a product"
},
"anotherNotImportant":{
"installments":null,
"baseRate":"30",
"grossComm":"20",
"totalMileage":null
},
"fleetdetails":{
"4b4bd441-a8eb-4754-9384-6f97d1ee23e3":{
"vehicleType":"43572386-5908-4e46-bf2b-3948df2e0e72",
"usage":"Class 3",
"carBand":"N\/A",
"coverType":"Third-Party Only",
"vehicleNumber":"1",
"modelRate":"222",
"technicalRate":"333",
"annualMileage":"444",
"adftExcess":"555",
"wsExcess":"777",
"annualBasePremium":null,
"usageRate":null
},
"cc12cc77-9346-4cae-8c27-6afc8b457f9b":{
"vehicleType":"fa999a90-b98f-499a-bef6-55b9a208c2fc",
"usage":"Class 1",
"carBand":"N\/A",
"coverType":"Comprehensive",
"vehicleNumber":"1",
"modelRate":"2",
"technicalRate":"3",
"annualMileage":"4",
"adftExcess":"5",
"wsExcess":"6",
"annualBasePremium":null,
"usageRate":null
}
}
}
I would like to select and get fleetdetails results like this:
item.id
vehicleType
usage
carBand
1
43572386-...
Class 3
N/A
1
fa999a90-...
Class 1
N/A
and so on.
How I can achieve this if I don't know fleetdetails json ids?
Combination of JSONB_EACH() and CROSS JOIN LATERAL will do the things for you try below query:
select
t1.id "id",
t2.value->>'vehicleType' "VEHICLETYPE",
t2.value->>'usage' "usage",
t2.value->>'carBand' "carBand",
t2.value->>'coverType' "coverType",
t2.value->>'vehicleNumber' "vehicleNumber",
t2.value->>'modelRate' "modelRate",
t2.value->>'technicalRate' "technicalRate",
t2.value->>'annualMileage' "annualMileage",
t2.value->>'adftExcess' "adftExcess",
t2.value->>'wsExcess' "wsExcess",
t2.value->>'annualBasePremium' "annualBasePremium",
t2.value->>'usageRate' "usageRate"
from items t1 cross join lateral jsonb_each(data->'fleetdetails') t2
DEMO
You can use jsonb_each():
select i.id,
d.detail ->> 'vehicleType' as vehicle_type,
d.detail ->> 'usage' as usage,
d.detail ->> 'carBand' as car_band
from item i
cross join jsonb_each(i.data -> 'fleetdetails') as d(key, detail);
This assumes that the column data is defined as jsonb (which it should be). If it's just a json column you have to use json_each() instead.

How to select query from json in Postgres

I have JSON data in field hotel_data like this:
{
"title":"foo",
"description":[
{
"locale":"pt",
"content":"pt text"
},
{
"locale":"fr",
"content":"fr text"
}
]
}
I would like to select description only fr description. It is possible using Postgres and how?
I was trying use ->> but it is not working...
SELECT
hotel_data->'description'->>'locale' = 'fr' AS description
FROM hotel LIMIT 1;
Note:
I don't want to use SELECT *...
Excepted output: {description: "fr text"}
You can use a lateral join and json_to_recordset to expand the json array as a set of records. Then, you can filter on column locale in the generated records, and finally recompose a new json object with your expected result:
select json_build_object('description', d.content) hotel_data_descr_fr
from
mytable,
json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'
Demo on DB Fiddle:
with mytable as (
select '{
"title":"foo",
"description":[
{
"locale":"pt",
"content":"pt text"
},
{
"locale":"fr",
"content":"fr text"
}
]
}'::json hotel_data
)
select json_build_object('description', d.content) hotel_data_descr_fr
from
mytable,
json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'
| hotel_data_descr_fr |
| :------------------------- |
| {"description": "fr text"} |
The filtering can be done using the #> operator which can use a GIN index on the hotel_data column. This is typically faster than expanding the array.
select ...
from hotel
where hotel_data #> '{"description": [{"locale":"fr"}] }';
This can also be extended to include more properties:
select ...
from hotel
where hotel_data #> '{"description": [{"locale":"fr", "headline": "nice view'}] }';
But you can only express equality conditions on the key/value pairs with that. Using LIKE is not possible. You will have to expand the array if you want to do that and apply the condition in the WHERE clause - see GMB's answer.
To extract that description, I would use a scalar sub-query:
select (select jsonb_build_object('description', t.descr ->> 'content')
from jsonb_array_elements(h.hotel_data -> 'description') as t(descr)
where t.descr ->> 'locale' = 'fr'
limit 1)
from hotel h
where h.hotel_data #> '{"description": [{"locale":"fr"}] }';
That way you don't need to expand the array for filtering which I expect to be faster if only a few hotels qualify for that condition. But it has the drawback that you need to repeat the condition on the locale in the sub-select.
The limit 1 is only a safety net in case you have more than one french description. If you never have that, it doesn't hurt either
With Postgres 12 this is easier:
select jsonb_build_object(
'description',
jsonb_path_query_first(hotel_data, '$.description ? (#.locale == "fr")') -> 'content'
)
from hotel
where hotel_data #> '{"description": [{"locale":"fr"}] }'
All of the above assumes hotel_data is a jsonb column, if it's not (which it should be) you need to cast it: hotel_data::jsonb

List arbitrary number of elements from a json field (postgres)

I have a json field in postgres db that contains data like
[{
"value": "+1 968 730 5680",
"label": "mobile",
"primary": true
},{
"value": "+1 909 169 4444",
"label": "mobile",
"primary": false
}]
I want to query this field to produce
+1 968 730 5680; +1 909 169 4444
Given that the number of elemets can vary.
I had enough brains to get the first element with
json_field -> 0 ->> 'value'
but am totally stuck at producing a string with all elements.
Please help.
UPDATE
Following advice below, I get an error with statement
select d ->> 'value' as val
from analyst.person
cross join jsonb_array_elements(phone) as x(d)
ERROR: column "phone" does not exist
LINE 3: cross join jsonb_array_elements(phone) as x(d)
another variant I tried
select d ->> 'value' as val
from analyst.person as person
cross join jsonb_array_elements(person.phone) as x(d)
gives error
ERROR: invalid reference to FROM-clause entry for table "person"
LINE 3: cross join jsonb_array_elements(person.phone) as x(d)
^
HINT: There is an entry for table "person", but it cannot be referenced from this part of the query.
You need to unnest your json array, extract the value and aggregate it back into a string:
select string_agg(val, '; ')
from (
select d ->> 'value' as val
from the_table
cross join lateral jsonb_array_elements(the_json_colum) as x(d)
) t
Online example: https://rextester.com/VFSRY99127