How to retrieve values from an array of json objects in PostgreSQL? - json

I have the following json :
[
{
"transition":"random_word",
"from":"paris",
"to":"porto",
"date":{
"date":"2020-05-28 11:51:25.201864",
"timezone_type":3,
"timezone":"Europe\/Paris"
}
},
{
"transition":"rainbow",
"from":"porto",
"to":"faro",
"date":{
"date":"2020-06-06 23:10:06.878539",
"timezone_type":3,
"timezone":"Europe\/Paris"
}
},
{
"transition":"banana",
"from":"faro",
"to":"rio_de_janeiro",
"date":{
"date":"2020-06-06 23:14:10.975099",
"timezone_type":3,
"timezone":"Europe\/Paris"
}
},
{
"transition":"hello",
"from":"rio_de_janeiro",
"to":"buenos_aires",
"date":{
"date":"2020-06-06 23:14:15.314370",
"timezone_type":3,
"timezone":"Europe\/Paris"
}
}
]
Imagine I want to retrieve the last stop of my traveler (the value of the key "to" from the last json object. Here : buenos_aires) and the date (here :2020-06-06 23:14:15.314370).
How should I proceed knowing that I want to do that using PostgreSQL?

If with "last" you mean the order in which the elements show up in the array, you can use jsonb_array_length() to get the length of the array and use that to obtain the last element:
select (the_json_column -> jsonb_array_length(the_json_column) - 1) ->> 'to' as "to",
(the_json_column -> jsonb_array_length(the_json_column) - 1) #>> '{date,date}' as "date"
from the_table
The expression jsonb_array_length(the_json_column) - 1 calculates the index of the "last" element in the array.
If your column is defined as json rather than jsonb (which it should be) you need to use the equivalent json_array_length() instead.

Related

How to parse multidimensional json array

I'm trying to retreive some specific data from a json stored in my database.
Here is my fidle : https://www.db-fiddle.com/f/5qZhsyddqJNej2NGj1x1hi/1
An exemple of a json string :
{
"complexProperties":[
{
"properties":{
"key":"Registred",
"Value":"123456789"
}
},
{
"properties":{
"key":"Urgency",
"Value":"Total"
}
},
{
"properties":{
"key":"ImpactScope",
"Value":"All"
}
}
]
}
In this case I need to retreive the value of Registred which is 123456789
Here is the request I tried to retreive first all value:
SELECT CAST(data AS jsonb)::json->>'complexProperties'->'properties' AS Registred FROM jsontesting
Query Error: error: operator does not exist: text -> unknown
You can use a JSON Path expression:
select jsonb_path_query_first(data, '$.complexProperties[*].properties ? (#.key == "Registred").Value')
from jsontesting;
This returns a jsonb value. If you need to convert that to a text value, use jsonb_path_query_first(...) #>> '{}'
Online example
An alternative that first flattens the JSON field (the arrj subquery) and then performs an old-school select. Using your jsontesting table -
select (j -> 'properties' ->> 'Value')
from
(
select json_array_elements(data::json -> 'complexProperties') as j
from jsontesting
) as arrj
where j -> 'properties' ->> 'key' = 'Registred';
Online example

How to read field from nested json?

this is my test json file.
{
"item" : {
"fracData" : [ ],
"fractimeData" : [ {
"number" : "1232323232",
"timePeriods" : [ {
"validFrom" : "2021-08-03"
} ]
} ],
"Module" : [ ]
}
}
This is how I read the json file.
starhist_test_df = spark.read.json("/mapr/xxx/yyy/ttt/dev/rawdata/Test.json", multiLine=True)
starhist_test_df.createOrReplaceTempView("v_test_df")
This query works.
df_test_01 = spark.sql("""
select item.fractimeData.number from v_test_df""")
df_test_01.collect();
Result
[Row(number=['1232323232'])]
But this query doesn't work.
df_test_01 = spark.sql("""
select item.fractimeData.timePeriods.validFrom from v_test_df""")
df_test_01.collect();
Error
cannot resolve 'v_test_df.`item`.`fractimeData`.`timePeriods`['validFrom']' due to data type mismatch: argument 2 requires integral type, however, ''validFrom'' is of string type.; line 3 pos 0;
What do I have to change, to read the validFrom field?
dot notation to access values works with struct or array<struct> types.
The schema for field number in item.fractimeData is string and accessing it via dot notation returns an array<string> since fractimeData is an array.
Similarly, the schema for field timePeriods in item.fractimeData is <array<struct<validFrom>>, and accessing it via dot notation wraps it into another array, resulting in final schema of array<array<struct<validFrom>>>.
The error you get is because the dot notation can work on array<struct> but not on array<array>.
Hence, flatten the result from item.fractimeData.timePeriods to get back an array<struct<validFrom>> and then apply the dot notation.
df_test_01 = spark.sql("""
select flatten(item.fractimeData.timePeriods).validFrom as validFrom from v_test_df""")
df_test_01.collect()
"""
[Row(validFrom=['2021-08-03', '2021-08-03'])]
"""

Postgres jsonb conditional replace of specific property in array of objects

Imagine I have a column data in a postgres table with the following sample data:
[
{
"type": "a",
"name": "Joe"
},
{
"type": "b",
"name": "John"
}
]
I want to perform an update on this table to update the type properties for each object in the json array, converting them from the current text to a corresponding number.
text "a" becomes 1
text "b" becomes 2
and so forth
I got as far as this:
update "table"
set "data" = jsonb_set("data", '{0,type}','1')
I understand this will update whichever object is at position 0 in the array to have value 1 in the type property, which is of course not what I want.
The replace needs to be conditional, if there is an a, it should become a 1, if there is a b, it should become a 2, etc..
Is there any way to accomplish what I'm looking for?
You can use JSONB_SET() function nested in JSONB_AGG() within an UPDATE Statement after producing consecutive integers through use of WITH ORDINALITY keywords following JSONB_ARRAY_ELEMENTS() function such as
UPDATE tab
SET data = (
SELECT JSONB_AGG(JSONB_SET(j, '{type}', ('"'||idx||'"')::JSONB))
FROM JSONB_ARRAY_ELEMENTS(data)
WITH ORDINALITY arr(j,idx)
)
Demo

Update JSON Array in Postgres with specific key

I have a complex array which look like following in a table column:
{
"sometag": {},
"where": [
{
"id": "Krishna",
"nick": "KK",
"values": [
"0"
],
"function": "ADD",
"numValue": [
"0"
]
},
{
"id": "Krishna1",
"nick": "KK1",
"values": [
"0"
],
"function": "SUB",
"numValue": [
"0"
]
}
],
"anotherTag": [],
"TagTag": {
"tt": "tttttt",
"tt1": "tttttt"
}
In this array, I want to update the function and numValue of id: "Krishna".
Kindly help.
This is really nasty because
Updating an element inside a JSON array always requires to expand the array
On-top: The array is nested
The identfier for the elements to update is a sibling not a parent, which means, you have to filter by a sibling
So I came up with a solution, but I want to disclaim: You should avoid doing this as regular database action! Better would be:
Parsing your JSON in the backend and do the operations in your backend code
Normalize the JSON in your database if that would be a common task, meaning: Create tables with appropriate columns and extract your JSON into the table structure. Do not store entire JSON objects in the database! That would make every single task much more easier and incredible more performant!
demo:db<>fiddle
SELECT
jsonb_set( -- 5
(SELECT mydata::jsonb FROM mytable),
'{where}',
updated_array
)::json
FROM (
SELECT
jsonb_agg( -- 4
CASE WHEN array_elem ->> 'id' = 'Krishna' THEN
jsonb_set( -- 3
jsonb_set(array_elem.value::jsonb, '{function}', '"ADDITION"'::jsonb), -- 2
'{numValue}',
'["0","1"]'::jsonb
)
ELSE array_elem::jsonb END
) as updated_array
FROM mytable,
json_array_elements(mydata -> 'where') array_elem -- 1
) s
Extract the nested array elements into one element per row
Replace function value. Note the casts from type json to type jsonb. That is necessary because there's no json_set() function but only jsonb_set(). Naturally, if you just have type jsonb, the casts are not necessary.
Replace numValue value
Reaggregate the array
Replace the where value of the original JSON object with the newly created array object.

Get Json key using JsonPath

I am struggling to write a JsonPath query to extract particular keys from the following sample Json.
{
"initial": "somevalue",
"somekey2": {
"inner1": "innerval1",
"inner2": "innerval2"
}
}
For example:
1) I wish to extract the first key, which in this case is initial. Is this possible using JsonPath?
2) Get an inner key such as inner1. Something similar to $."initial"."somekey2" but returning an array with just the keys (inner1 and inner2).
This SO question covers it.
$.*~ returns
[
"initial",
"somekey2"
]
$.somekey2.*~ returns
[
"inner1",
"inner2"
]
To get all 2nd order children use $.*.*~. Basically for nth order, $.(n times *).*~
not sure about json path, but suppose your object is a
a={
"initial": "somevalue",
"somekey2": {
"inner1": "innerval1",
"inner2": "innerval2"
}
};
the you can get all keys by using Object.keys(a)
that will give array of keys ["initial", "somekey2"]
then you can use that key to revtrive its nested value
a[Object.keys(a)[1]] // returns {inner1: "innerval1", inner2: "innerval2"}
and you can repeat the same for all nested element