SQL Server: How to flatten nested arrays by merging values using - sql

I have 10000 jsons with different ids each has 10000 names. How to flatten nested arrays by merging values usin SQL Server? Jsons can be read in any language, I'm looking for any SQL dialect that can transform the data as I'm using spark connectors. I use many SQL dialects including not limiting Spark SQL, Postgresql,MySql, SQLite and SQL Server...
NOTE: I was asked by Martijn Pieters to create duplicates to be specific for each SQL dialect so this is for SQL Server.
Notes:
Input dataframe has more than 10000 columns name_1_a, name_1000_xx so column(array) names can not be hardcoded as it will requires to write 10000 names
id, date, val has always the same naming convention across all columns and all jsons
array size can vary but date, val are always there so they can be hardcoded
date can be different in each array, for example name_1_a starts with 2001, but name_10000_xvz for id == 1 starts with 2000 and finnish with 2004, however for id == 2 starts with 1990 and finish with 2004
Input df:
root
|-- id: long (nullable = true)
|-- name_10000_xvz: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- date: long (nullable = true)
| | |-- val: long (nullable = true)
|-- name_1_a: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- date: long (nullable = true)
| | |-- val: long (nullable = true)
|-- name_1_b: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- date: long (nullable = true)
| | |-- val: long (nullable = true)
|-- name_2_a: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- date: long (nullable = true)
| | |-- val: long (nullable = true)
+---+------------------------------------------------------------------------+---------------------------------+---------------------------------+------------------------------------+
|id |name_10000_xvz |name_1_a |name_1_b |name_2_a |
+---+------------------------------------------------------------------------+---------------------------------+---------------------------------+------------------------------------+
|2 |[{1990, 39}, {2000, 30}, {2001, 31}, {2002, 32}, {2003, 33}, {2004, 34}]|[{2001, 1}, {2002, 2}, {2003, 3}]|[{2001, 4}, {2002, 5}, {2003, 6}]|[{2001, 21}, {2002, 22}, {2003, 23}]|
|1 |[{2000, 30}, {2001, 31}, {2002, 32}, {2003, 33}] |[{2001, 1}, {2002, 2}, {2003, 3}]|[{2001, 4}, {2002, 5}, {2003, 6}]|[{2001, 21}, {2002, 22}, {2003, 23}]|
+---+------------------------------------------------------------------------+---------------------------------+---------------------------------+------------------------------------+
Required output df:
+---+---------+----------+-----------+---------+----------------+
|id | date | name_1_a | name_1_b |name_2_a | name_10000_xvz |
+---+---------+----------+-----------+---------+----------------+
|1 | 2000 | 0 | 0 | 0 | 30 |
|1 | 2001 | 1 | 4 | 21 | 31 |
|1 | 2002 | 2 | 5 | 22 | 32 |
|1 | 2003 | 3 | 6 | 23 | 33 |
|2 | 1990 | 0 | 0 | 0 | 39 |
|2 | 2000 | 0 | 0 | 0 | 30 |
|2 | 2001 | 1 | 4 | 21 | 31 |
|2 | 2002 | 2 | 5 | 22 | 32 |
|2 | 2003 | 3 | 6 | 23 | 33 |
|2 | 2004 | 0 | 0 | 0 | 34 |
+---+---------+----------+-----------+---------+----------------+
Below are jsons for input df:
1.json
{ "id": 1, "name_1_a": [ { "date": 2001, "val": 1 }, { "date": 2002, "val": 2 }, { "date": 2003, "val": 3 } ], "name_1_b": [ { "date": 2001, "val": 4 }, { "date": 2002, "val": 5 }, { "date": 2003, "val": 6 } ], "name_2_a": [ { "date": 2001, "val": 21 }, { "date": 2002, "val": 22 }, { "date": 2003, "val": 23 } ], "name_10000_xvz": [ { "date": 2000, "val": 30 }, { "date": 2001, "val": 31 }, { "date": 2002, "val": 32 }, { "date": 2003, "val": 33 } ]}
2.json
{ "id": 2, "name_1_a": [ { "date": 2001, "val": 1 }, { "date": 2002, "val": 2 }, { "date": 2003, "val": 3 } ], "name_1_b": [ { "date": 2001, "val": 4 }, { "date": 2002, "val": 5 }, { "date": 2003, "val": 6 } ], "name_2_a": [ { "date": 2001, "val": 21 }, { "date": 2002, "val": 22 }, { "date": 2003, "val": 23 } ], "name_10000_xvz": [ { "date": 1990, "val": 39 }, { "date": 2000, "val": 30 }, { "date": 2001, "val": 31 }, { "date": 2002, "val": 32 }, { "date": 2003, "val": 33 }, { "date": 2004, "val": 34 } ]}}

OK, so we have 2 "problems" we need to solve here. Firstly, the fact that you need a dynamic number of columns as you don't know what names are you your data. This means you need dynamic SQL.
Next is the problem that not every name has a value for every year, so we need to also have a "year" table we can LEFT JOIN from so that we have a row for every name.
This, as a result, is going to be really messy, but it can be done. I've left comments where I can on this, but the best thing i can really suggest is taking the time to read the SQL, PRINTing/SELECTing the dynamic statement, and learning what it does.
First let's build a static version, so you can see what it would look like. So here I use a CTE to get all the years, and then another to get the data in a normalised format from the JSON. Finally unpivot the data using condititional aggregation:
--Sample JSON
DECLARE #JSON nvarchar(MAX) = N'{ "id": 1, "name_1_a": [ { "date": 2001, "val": 1 }, { "date": 2002, "val": 2 }, { "date": 2003, "val": 3 } ], "name_1_b": [ { "date": 2001, "val": 4 }, { "date": 2002, "val": 5 }, { "date": 2003, "val": 6 } ], "name_2_a": [ { "date": 2001, "val": 21 }, { "date": 2002, "val": 22 }, { "date": 2003, "val": 23 } ], "name_10000_xvz": [ { "date": 2000, "val": 30 }, { "date": 2001, "val": 31 }, { "date": 2002, "val": 32 }, { "date": 2003, "val": 33 } ]}';
--Get distinct Years
WITH Years AS(
SELECT DISTINCT V.date
FROM OPENJSON(#JSON) J
CROSS APPLY (SELECT *
FROM OPENJSON(J.[value])
WITH(date int)
WHERE ISJSON(J.[value]) = 1) V),
--Get Data
Data AS(
SELECT J.[key] AS [name],
V.date,
V.val
FROM OPENJSON(#JSON) J
CROSS APPLY (SELECT *
FROM OPENJSON(J.[value])
WITH(date int,
val int)
WHERE ISJSON(J.[value]) = 1) V)
--Final Select and Unpivot
SELECT JSON_VALUE(#JSON, '$.id') AS ID,
Y.Date,
ISNULL(MAX(CASE D.[name] WHEN 'name_1_a' THEN D.val END),0) AS name_1_a,
ISNULL(MAX(CASE D.[name] WHEN 'name_1_b' THEN D.val END),0) AS name_1_b,
ISNULL(MAX(CASE D.[name] WHEN 'name_2_a' THEN D.val END),0) AS name_2_a,
ISNULL(MAX(CASE D.[name] WHEN 'name_10000_xvz' THEN D.val END),0) AS name_10000_xvz
FROM Years Y
LEFT JOIN Data D ON Y.Date = D.Date
GROUP BY Y.Date;
As I mentioned, however, this isn't dynamic. This is, therefore where it gets a little more messy. I, for the below, am assuming you're using a recent version of SQL Server, and thus have access to STRING_AGG (if not, you'll need to use the old FOR XML PATH and STUFF method):
--Sample JSON
DECLARE #JSON nvarchar(MAX) = N'{ "id": 1, "name_1_a": [ { "date": 2001, "val": 1 }, { "date": 2002, "val": 2 }, { "date": 2003, "val": 3 } ], "name_1_b": [ { "date": 2001, "val": 4 }, { "date": 2002, "val": 5 }, { "date": 2003, "val": 6 } ], "name_2_a": [ { "date": 2001, "val": 21 }, { "date": 2002, "val": 22 }, { "date": 2003, "val": 23 } ], "name_10000_xvz": [ { "date": 2000, "val": 30 }, { "date": 2001, "val": 31 }, { "date": 2002, "val": 32 }, { "date": 2003, "val": 33 } ]}';
--Variables for dynamic SQL
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE #Delimiter varchar(20) = N',' + #CRLF + N' ';
--You'll note the start is all the same
SET #SQL = N'--Get disinct Years' + #CRLF +
N'WITH Years AS(' + #CRLF +
N' SELECT DISTINCT V.date' + #CRLF +
N' FROM OPENJSON(#JSON) J' + #CRLF +
N' CROSS APPLY (SELECT *' + #CRLF +
N' FROM OPENJSON(J.[value]) ' + #CRLF +
N' WITH(date int) ' + #CRLF +
N' WHERE ISJSON(J.[value]) = 1) V),' + #CRLF +
N'--Get Data' + #CRLF +
N'Data AS(' + #CRLF +
N' SELECT J.[key] AS [name],' + #CRLF +
N' V.date,' + #CRLF +
N' V.val ' + #CRLF +
N' FROM OPENJSON(#JSON) J' + #CRLF +
N' CROSS APPLY (SELECT *' + #CRLF +
N' FROM OPENJSON(J.[value]) ' + #CRLF +
N' WITH(date int,' + #CRLF +
N' val int) ' + #CRLF +
N' WHERE ISJSON(J.[value]) = 1) V)' + #CRLF +
N'--Final Select and Unpivot' + #CRLF +
N'SELECT JSON_VALUE(#JSON, ''$.id'') AS ID,' + #CRLF +
N' Y.Date,' + #CRLF +
(SELECT STRING_AGG(N'ISNULL(MAX(CASE D.[name] WHEN ' + QUOTENAME(J.[key],'''') + N' THEN D.val END),0) AS ' + QUOTENAME(J.[key]),#Delimiter)
FROM OPENJSON(#JSON) J) + #CRLF +
N'FROM Years Y' + #CRLF +
N' LEFT JOIN Data D ON Y.Date = D.Date' + #CRLF +
N'GROUP BY Y.Date;';
PRINT #SQL; --YOur best friend for debugging
EXEC sys.sp_executesql #SQL, N'#JSON nvarchar(MAX)', #JSON;
db<>fiddle

Related

MariaDB extract json nested data

I have following SQL query and trying to extract nested json data field.
*************************** 2. row ***************************
created_at: 2023-01-05 14:25:52
updated_at: 2023-01-05 14:26:02
deleted_at: NULL
deleted: 0
id: 2
instance_uuid: ef6380b4-5455-48f8-9e4b-3d04199be3f5
numa_topology: NULL
pci_requests: []
flavor: {"cur": {"nova_object.name": "Flavor", "nova_object.namespace": "nova", "nova_object.version": "1.2", "nova_object.data": {"id": 2, "name": "tempest2", "memory_mb": 512, "vcpus": 1, "root_gb": 1, "ephemeral_gb": 0, "flavorid": "202", "swap": 0, "rxtx_factor": 1.0, "vcpu_weight": 0, "disabled": false, "is_public": true, "extra_specs": {}, "description": null, "created_at": "2023-01-05T05:30:36Z", "updated_at": null, "deleted_at": null, "deleted": false}}, "old": null, "new": null}
vcpu_model: {"nova_object.name": "VirtCPUModel", "nova_object.namespace": "nova", "nova_object.version": "1.0", "nova_object.data": {"arch": null, "vendor": null, "topology": {"nova_object.name": "VirtCPUTopology", "nova_object.namespace": "nova", "nova_object.version": "1.0", "nova_object.data": {"sockets": 1, "cores": 1, "threads": 1}, "nova_object.changes": ["cores", "threads", "sockets"]}, "features": [], "mode": "host-model", "model": null, "match": "exact"}, "nova_object.changes": ["mode", "model", "vendor", "features", "topology", "arch", "match"]}
migration_context: NULL
keypairs: {"nova_object.name": "KeyPairList", "nova_object.namespace": "nova", "nova_object.version": "1.3", "nova_object.data": {"objects": []}}
device_metadata: NULL
trusted_certs: NULL
vpmems: NULL
resources: NULL
In flavor: section i have some json data and i am trying to extract "name": "tempest2" value in my question but it's nested so i am not able to find way to extract that value.
My query but how do i remove [] square brackets in value
MariaDB [nova]> select uuid, instances.created_at, instances.deleted_at, json_extract(flavor, '$.cur.*.name') AS FLAVOR from instances join instance_extra on instances.uuid = instance_extra.instance_uuid;
+--------------------------------------+---------------------+---------------------+--------------+
| uuid | created_at | deleted_at | FLAVOR |
+--------------------------------------+---------------------+---------------------+--------------+
| edb0facb-3353-4848-82e2-f12701a0a3aa | 2023-01-05 05:37:13 | 2023-01-05 05:37:49 | ["tempest1"] |
| ef6380b4-5455-48f8-9e4b-3d04199be3f5 | 2023-01-05 14:25:51 | NULL | ["tempest2"] |
+--------------------------------------+---------------------+---------------------+--------------+
#Update
This is the MariaDB version I have
MariaDB [nova]> SELECT VERSION();
+-------------------------------------------+
| VERSION() |
+-------------------------------------------+
| 10.5.12-MariaDB-1:10.5.12+maria~focal-log |
+-------------------------------------------+
1 row in set (0.000 sec)

SQL-Query to get nested JSON Array

I have the following sample data in a MS-SQL database:
(Microsoft SQL Server Standard Version 13; Microsoft SQL Server Management Studio 18)
+----------+-----------+-----+--------+---------+---------+
| LastName | Firstname | Age | Weight | Sallery | Married |
+----------+-----------+-----+--------+---------+---------+
| Smith | Stan | 58 | 87 | 59.000 | true |
| Smith | Maria | 53 | 57 | 45.000 | true |
| Brown | Chris | 48 | 77 | 159.000 | true |
| Brown | Stepahnie | 39 | 67 | 95.000 | true |
| Brown | Angela | 12 | 37 | 0.0 | false |
+----------+-----------+-----+--------+---------+---------+
I want to get a nested JSON array from it that looks like this:
[
{
"Smith": [
{
"Stan": [
{
"Age": 58,
"Weight": 87,
"Sallery": 59.000,
"Married": true
}
],
"Maria": [
{
"Age": 53,
"Weight": 57,
"Sallery": 45.000,
"Married": true
}
]
}
],
"Brown": [
{
"Chris": [
{
"Age": 48,
"Weight": 77,
"Sallery": 159.000,
"Married": true
}
],
"Stepahnie": [
{
"Age": 39,
"Weight": 67,
"Sallery": 95.000,
"Married": true
}
],
"Angela": [
{
"Age": 12,
"Weight": 37,
"Sallery": 0.0,
"Married": false
}
]
}
]
}
]
How do I have to build the SQL query?
I have tried different ways but I don't get to dynamize the root or the root keeps repeating itself....
For example, I tried the following query:
I get one Level with:
WITH cte AS
(
SELECT FirstName
js = json_query(
(
SELECT Age,
Weight,
Sallery,
Married
FOR json path,
without_array_wrapper ) )
FROM Table1)
SELECT '[' + stuff(
(
SELECT '},{"' + FirstName + '":' + '[' + js + ']'
FROM cte
FOR xml path ('')), 1, 2, '') + '}]'
But I need one more nested level with LastName
Another try:
SELECT
LastName ,json
FROM Table1 as a
OUTER APPLY (
SELECT
FirstName
FROM Table1 as b
WHERE a.LastName = b.LastName
FOR JSON PATH
) child(json)
FOR JSON PATH
Unfortunately, SQL Server does not support JSON_AGG nor JSON_OBJECT_AGG, which would have helped here. But we can hack it with STRING_AGG and STRING_ESCAPE
WITH ByFirstName AS
(
SELECT
p.LastName,
p.FirstName,
json = STRING_AGG(j.json, ',')
FROM Person p
CROSS APPLY (
SELECT
p.Age,
p.Weight,
p.Sallery,
p.Married
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) AS j(json)
GROUP BY
p.LastName,
p.FirstName
),
ByLastName AS
(
SELECT
p.LastName,
json = STRING_AGG(CONCAT(
'"',
STRING_ESCAPE(p.FirstName, 'json'),
'":[',
p.json,
']'
), ',')
FROM ByFirstName p
GROUP BY
p.LastName
)
SELECT '[{' +
STRING_AGG(CONCAT(
'"',
STRING_ESCAPE(p.LastName, 'json'),
'":{',
p.json,
'}'
), ',') + '}]'
FROM ByLastName p
db<>fiddle
This gets you
[
{
"Brown": {
"Angela": [
{
"Age": 12,
"Weight": 37,
"Sallery": 0,
"Married": false
}
],
"Chris": [
{
"Age": 48,
"Weight": 77,
"Sallery": 159000,
"Married": true
}
],
"Stepahnie": [
{
"Age": 39,
"Weight": 67,
"Sallery": 95000,
"Married": true
}
]
},
"Smith": {
"Maria": [
{
"Age": 53,
"Weight": 57,
"Sallery": 45000,
"Married": true
}
],
"Stan": [
{
"Age": 58,
"Weight": 87,
"Sallery": 59000,
"Married": true
}
]
}
}
]
It's certainly possible to get your desired JSON output but, as you can see below, the code is rather convoluted...
/*
* Data setup...
*/
create table dbo.Person (
LastName varchar(10),
FirstName varchar(10),
Age int,
Weight int,
Sallery int,
Married bit
);
insert dbo.Person (LastName, FirstName, Age, Weight, Sallery, Married)
values
('Smith', 'Stan', 58, 87, 59000, 1),
('Smith', 'Maria', 53, 57, 45000, 1),
('Brown', 'Chris', 48, 77, 159000, 1),
('Brown', 'Stepahnie', 39, 67, 95000, 1),
('Brown', 'Angela', 12, 37, 0, 0);
/*
* Example JSON query...
*/
with Persons as (
select LastName, Stan, Maria, Chris, Stepahnie, Angela
from (
select
LastName,
FirstName,
(
select Age, Weight, Sallery, Married
for json path
) as data
from dbo.Person
) src
pivot (max(data) for FirstName in (Stan, Maria, Chris, Stepahnie, Angela)) pvt
)
select
json_query((
select
json_query(Stan) as Stan,
json_query(Maria) as Maria
from Persons
where LastName = 'Smith'
for json path
)) as Smith,
json_query((
select
json_query(Chris) as Chris,
json_query(Stepahnie) as Stepahnie,
json_query(Angela) as Angela
from Persons
where LastName = 'Brown'
for json path
)) as Brown
for json path;
Which yields the output...
[
{
"Smith": [
{
"Stan": [
{
"Age": 58,
"Weight": 87,
"Sallery": 59000,
"Married": true
}
],
"Maria": [
{
"Age": 53,
"Weight": 57,
"Sallery": 45000,
"Married": true
}
]
}
],
"Brown": [
{
"Chris": [
{
"Age": 48,
"Weight": 77,
"Sallery": 159000,
"Married": true
}
],
"Stepahnie": [
{
"Age": 39,
"Weight": 67,
"Sallery": 95000,
"Married": true
}
],
"Angela": [
{
"Age": 12,
"Weight": 37,
"Sallery": 0,
"Married": false
}
]
}
]
}
]

PLSQL: remove unwanted double quotes in a Json

I have a Json like this (it is contained in a clob variable):
{"id": "33", "type": "abc", "val": "2", "cod": "", "sg1": "1", "sg2": "1"}
{"id": "359", "type": "abcef", "val": "52", "cod": "aa", "sg1": "", "sg2": "0"}
…
I need to remove " from values of: id, val, sg1, sg2
Is it possibile?
For example, I need to obtain this:
{"id": 33, "type": "abc", "val": 2, "cod": "", "sg1": 1, "sg2": 1}
{"id": 359, "type": "abcef", "val": 52, "cod": "aa", "sg1": , "sg2": 0}
…
If you are using Oracle 12 (R2?) or later then you can convert your JSON to the appropriate data types and then convert it back to JSON.
Oracle 18 Setup:
CREATE TABLE test_data ( value CLOB );
INSERT INTO test_data ( value )
VALUES ( '{"id": "33", "type": "abc", "val": "2", "cod": "", "sg1": "1", "sg2": "1"}' );
INSERT INTO test_data ( value )
VALUES ( '{"id": "359", "type": "abcef", "val": "52", "cod": "aa", "sg1": "", "sg2": "0"}' );
Query:
SELECT JSON_OBJECT(
'id' IS j.id,
'type' IS j.typ,
'val' IS j.val,
'cod' IS j.cod,
'sg1' IS j.sg1,
'sg2' IS j.sg2
) AS JSON
FROM test_data t
CROSS JOIN
JSON_TABLE(
t.value,
'$'
COLUMNS
id NUMBER(5,0) PATH '$.id',
typ VARCHAR2(10) PATH '$.type',
val NUMBER(5,0) PATH '$.val',
cod VARCHAR2(10) PATH '$.cod',
sg1 NUMBER(5,0) PATH '$.sg1',
sg2 NUMBER(5,0) PATH '$.sg2'
) j
Output:
| JSON |
| :--------------------------------------------------------------- |
| {"id":33,"type":"abc","val":2,"cod":null,"sg1":1,"sg2":1} |
| {"id":359,"type":"abcef","val":52,"cod":"aa","sg1":null,"sg2":0} |
Or, if you want to use regular expressions (you shouldn't if you have the choice and should use a proper JSON parser instead) then:
Query 2:
SELECT REGEXP_REPLACE(
REGEXP_REPLACE(
value,
'"(id|val|sg1|sg2)": ""',
'"\1": "null"'
),
'"(id|val|sg1|sg2)": "(\d+|null)"',
'"\1": \2'
) AS JSON
FROM test_data
Output:
| JSON |
| :-------------------------------------------------------------------------- |
| {"id": 33, "type": "abc", "val": 2, "cod": "", "sg1": 1, "sg2": 1} |
| {"id": 359, "type": "abcef", "val": 52, "cod": "aa", "sg1": null, "sg2": 0} |
db<>fiddle here

Postgres Build Complex JSON Object from Wide Column Like Design to Key Value

I could really use some help here before my mind explodes...
Given the following data structure:
SELECT * FROM (VALUES (1, 1, 1, 1), (2, 2, 2, 2)) AS t(day, apple, banana, orange);
day | apple | banana | orange
-----+-------+--------+--------
1 | 1 | 1 | 1
2 | 2 | 2 | 2
I want to construct a JSON object which looks like the following:
{
"data": [
{
"day": 1,
"fruits": [
{
"key": "apple",
"value": 1
},
{
"key": "banana",
"value": 1
},
{
"key": "orange",
"value": 1
}
]
}
]
}
Maybe I am not so far away from my goal:
SELECT json_build_object(
'data', json_agg(
json_build_object(
'day', t.day,
'fruits', t)
)
) FROM (VALUES (1, 1, 1, 1), (2, 2, 2, 2)) AS t(day, apple, banana, orange);
Results in:
{
"data": [
{
"day": 1,
"fruits": {
"day": 1,
"apple": 1,
"banana": 1,
"orange": 1
}
}
]
}
I know that there is json_each which may do the trick. But I am struggling to apply it to the query.
Edit:
This is my updated query which, I guess, is pretty close. I have dropped the thought to solve it with json_each. Now I only have to return an array of fruits instead appending to the fruits object:
SELECT json_build_object(
'data', json_agg(
json_build_object(
'day', t.day,
'fruits', json_build_object(
'key', 'apple',
'value', t.apple,
'key', 'banana',
'value', t.banana,
'key', 'orange',
'value', t.orange
)
)
)
) FROM (VALUES (1, 1, 1, 1), (2, 2, 2, 2)) AS t(day, apple, banana, orange);
Would I need to add a subquery to prevent a nested aggregate function?
Use the function jsonb_each() to get pairs (key, value), so you do not have to know the number of columns and their names to get a proper output:
select jsonb_build_object('data', jsonb_agg(to_jsonb(s) order by day))
from (
select day, jsonb_agg(jsonb_build_object('key', key, 'value', value)) as fruits
from (
values (1, 1, 1, 1), (2, 2, 2, 2)
) as t(day, apple, banana, orange),
jsonb_each(to_jsonb(t)- 'day')
group by 1
) s;
The above query gives this object:
{
"data": [
{
"day": 1,
"fruits": [
{
"key": "apple",
"value": 1
},
{
"key": "banana",
"value": 1
},
{
"key": "orange",
"value": 1
}
]
},
{
"day": 2,
"fruits": [
{
"key": "apple",
"value": 2
},
{
"key": "banana",
"value": 2
},
{
"key": "orange",
"value": 2
}
]
}
]
}

multiply a value of each item of a json array with postgres 9.6

I tried many different things that I gathered here and there (official docs, blog posts, SO, …) but didn't succeed, so here's my question to you all:
Given this table:
basik=# select id, jsonb_pretty(range_price_list_values::jsonb) from product;
id | jsonb_pretty
--------------------------------------+--------------------------
cc80c862-c264-4bfe-a929-a52478c8d59e | [ +
| { +
| "to": 10, +
| "from": 5, +
| "price": 1 +
| }, +
| { +
| "to": 20, +
| "from": 15, +
| "price": 1298000+
| }, +
| { +
| "to": 30, +
| "from": 25, +
| "price": 500000 +
| } +
| ]
How to multiply by 1000 the price key of each element of each row of the table ?
PS: my failed tentative was to look around jsonb_* functions and window functions:
WITH prices as (select id, jsonb_array_elements(range_price_list_values::jsonb) from product)
UPDATE product SET range_price_list_values = JSONB_SET(
range_price_list_values::jsonb,
'{' || price.rank || ',price}', jsonb_extract_path('{' || price.rank || ',price}')::int * 1000, false
)::json;
Thanks for taking time to read! :)
You'll need a sub-select, as you want to update multiple fields in your JSON:
update product
set range_price_list_values = (
select jsonb_agg(case
when jsonb_typeof(elem -> 'price') = 'number'
then jsonb_set(elem, array['price'], to_jsonb((elem ->> 'price')::numeric * 1000))
else elem
end)
from jsonb_array_elements(range_price_list_values::jsonb) elem
)::json;
Note: this will only update numeric price keys, otherwise an exception would be thrown, when a price is not a number.
http://rextester.com/PQN70851
First that came (quite ugly):
t=# create table product (id text, range_price_list_values jsonb);
CREATE TABLE
t=# insert into product select 'cc80c862-c264-4bfe-a929-a52478c8d59e','[
t'# {
t'# "to": 10,
t'# "from": 5,
t'# "price": 1
t'# },
t'# {
t'# "to": 20,
t'# "from": 15,
t'# "price": 1298000
t'# },
t'# {
t'# "to": 30,
t'# "from": 25,
t'# "price": 500000
t'# }
t'# ]';
INSERT 0 1
t=# with b as (with a as (select id, jsonb_array_elements(range_price_list_values::jsonb) j from product) select id,jsonb_set(j,'{price}',((j->>'price')::int * 1000)::text::jsonb) from a) select distinct id, jsonb_pretty(concat('[',string_agg(jsonb_set::text,',') over (partition by id),']')::jsonb) from b;
id | jsonb_pretty
--------------------------------------+-----------------------------
cc80c862-c264-4bfe-a929-a52478c8d59e | [ +
| { +
| "to": 10, +
| "from": 5, +
| "price": 1000 +
| }, +
| { +
| "to": 20, +
| "from": 15, +
| "price": 1298000000+
| }, +
| { +
| "to": 30, +
| "from": 25, +
| "price": 500000000 +
| } +
| ]
(1 row)
having that in CTE, you can update values against it