I would like to update fields of the following JSON array (stored in a column with JSONB datatype) based on the objectId.
[
{
objectId: 'gDKn1jM5d',
objectType: 'type1',
posX: 50,
posY: 100,
},
{
objectId: '4dg5E8BDv',
objectType: 'type2',
posX: 50,
posY: 100,
},
{
objectId: 'ZmCwOf5N2',
objectType: 'type3',
posX: 100,
posY: 150,
}
]
In Mongodb I can use a simple update statement but I was not able to find a way in postgres.
For example I would like to update all array elements with objectId 'ZmCwOf5N2' to the posX value 300 (that means it would only affect the 3rd array item).
I'm looking for a plain SQL statement in order to execute the update.
The postgres version is 11.
It is not possible for me to install extensions because I'm using a database as a service provider. However, in case there is no easy way to accomplish the update statement, I would be able to add a postgres function using e.g. C code.
UPDATE tbl t
SET js =
(
SELECT jsonb_agg(CASE WHEN elem->>'objectId' = 'ZmCwOf5N2'
THEN jsonb_set(elem, '{posX}', to_jsonb(int '300'))
ELSE elem
END) AS js1
FROM jsonb_array_elements(t.js) elem
)
WHERE t.js #> '[{"objectId": "ZmCwOf5N2"}]';
Note that this ..
adds the 'posX' key if it's missing
updates rows even where nothing changes
To only update existing keys and only update the row if the update actually changes the value:
UPDATE tbl t
SET js =
(
SELECT jsonb_agg(CASE WHEN elem->>'objectId' = 'ZmCwOf5N2'
THEN jsonb_set(elem, '{posX}', to_jsonb(int '300'), false) -- !
ELSE elem
END) AS js1
FROM jsonb_array_elements(t.js) elem
)
WHERE t.js #> '[{"objectId": "ZmCwOf5N2"}]'
AND js <>
(
SELECT jsonb_agg(CASE WHEN elem->>'objectId' = 'ZmCwOf5N2'
THEN jsonb_set(elem, '{posX}', to_jsonb(int '300'), false)
ELSE elem
END) AS js1
FROM jsonb_array_elements(t.js) elem
); --!
See:
How to update complex jsonb column?
Update key value in jsonb array of objects
Related
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.
Using PostgreSQL 13.4 I have a table with a JSON column in a structure like the following sample:
{
"username": "jsmith",
"location": "United States",
"posts": [
{
"id":"1",
"title":"Welcome",
"newKey":true <----------- insert new key/value pair here
},
{
"id":"4",
"title":"What started it all",
"newKey":true <----------- insert new key/value pair here
}
]
}
For changing keys on the first level, I used a simple query like this
UPDATE
sample_table_json
SET
json = json::jsonb || '{"active": true}';
But this doesn't work for nested objects and objects in an array like in the sample.
How would I insert a key/value pair into a JSON column with nested objects in an array?
You have to use the jsonb_set function while specifying the right path see the manual.
For a single json update :
UPDATE sample_table_json
SET json = jsonb_set( json::jsonb
, '{post,0,active}'
, 'true'
, true
)
For a (very) limited set of json updates :
UPDATE sample_table_json
SET json = jsonb_set(jsonb_set( json::jsonb
, '{post,0,active}'
, 'true'
, true
)
, '{post,1,active}'
, 'true'
, true
)
For a larger set of json updates of the same json data, you can create the "aggregate version" of the jsonb_set function :
CREATE OR REPLACE FUNCTION jsonb_set(x jsonb, y jsonb, p text[], e jsonb, b boolean)
RETURNS jsonb LANGUAGE sql AS $$
SELECT jsonb_set(COALESCE(x,y), p, e, b) ; $$ ;
CREATE OR REPLACE AGGREGATE jsonb_set_agg(x jsonb, p text[], e jsonb, b boolean)
( STYPE = jsonb, SFUNC = jsonb_set) ;
and then use the new aggregate function jsonb_set_agg while iterating on a query result where the path and val fields could be calculated :
SELECT jsonb_set_agg('{"username": "jsmith","location": "United States","posts": [{"id":"1","title":"Welcome"},{"id":"4","title":"What started it all"}]}' :: jsonb
, l.path :: text[]
, to_jsonb(l.val)
, true)
FROM (VALUES ('{posts,0,active}', 'true'), ('{posts,1,active}', 'true')) AS l(path, val) -- this list could be the result of a subquery
This query could finally be used in order to update some data :
WITH list AS
(
SELECT id
, jsonb_set_agg(json :: jsonb
, l.path :: text[]
, to_jsonb(l.val)
, true) AS res
FROM sample_table_json
CROSS JOIN (VALUES ('{posts,0,active}', 'true'), ('{posts,1,active}', 'true')) AS l(path, val)
GROUP BY id
)
UPDATE sample_table_json AS t
SET json = l.res
FROM list AS l
WHERE t.id = l.id
see the test result in dbfiddle
It became a bit complicated. Loop through the array, add the new key/value pair to each array element and re-aggregate the array, then rebuild the whole object.
with t(j) as
(
values ('{
"username": "jsmith",
"location": "United States",
"posts": [
{
"id":"1", "title":"Welcome", "newKey":true
},
{
"id":"4", "title":"What started it all", "newKey":true
}]
}'::jsonb)
)
select j ||
jsonb_build_object
(
'posts',
(select jsonb_agg(je||'{"active":true}') from jsonb_array_elements(j->'posts') je)
)
from t;
Given the following two table columns jsonb type:
dividend_actual
{
"dividends": [
{
"amount": "2.9800",
"balanceDate": "2020-06-30T00:00:00Z"
},
{
"amount": "4.3100",
"balanceDate": "2019-06-30T00:00:00Z"
}
],
"lastUpdated": "2020-11-16T14:50:51.289649512Z",
"providerUpdateDate": "2020-11-16T00:00:00Z"
}
dividend_forecast
{
"dividends": [
{
"amount": "2.3035",
"balanceDate": "2021-06-01T00:00:00Z"
},
{
"amount": "3.0452",
"balanceDate": "2022-06-01T00:00:00Z"
},
{
"amount": "3.1845",
"balanceDate": "2023-06-01T00:00:00Z"
}
],
"lastForecasted": "2020-11-13T00:00:00Z",
"providerUpdateDate": "2020-11-16T00:00:00Z"
}
I would like to merge both dividends arrays from dividend_actual and dividend_forecast, but before merging them I want to add an extra field (forecast) on every single object.
I did try the following:
SELECT
dividends
FROM
stock_financial AS f
INNER JOIN instrument AS i ON i.id = f.instrument_id,
jsonb_array_elements(
(f.dividend_forecast->'dividends' || jsonb '{"forecast": true}') ||
(f.dividend_actual->'dividends' || jsonb '{"forecast": false}')
) AS dividends
WHERE
i.symbol = 'ASX_CBA'
ORDER BY
dividends ->>'balanceDate' DESC;
The above query gives me the following results:
{"forecast":true}
{"forecast":false}
{"amount":"3.1845","balanceDate":"2023-06-01T00:00:00Z"}
{"amount":"3.0452","balanceDate":"2022-06-01T00:00:00Z"}
{"amount":"2.3035","balanceDate":"2021-06-01T00:00:00Z"}
{"amount":"2.9800","balanceDate":"2020-06-30T00:00:00Z"}
{"amount":"4.3100","balanceDate":"2019-06-30T00:00:00Z"}
But what I need instead is the following output:
{"amount":"3.1845","balanceDate":"2023-06-01T00:00:00Z","forecast":true}
{"amount":"3.0452","balanceDate":"2022-06-01T00:00:00Z","forecast":true}
{"amount":"2.3035","balanceDate":"2021-06-01T00:00:00Z","forecast":true}
{"amount":"2.9800","balanceDate":"2020-06-30T00:00:00Z","forecast":false}
{"amount":"4.3100","balanceDate":"2019-06-30T00:00:00Z","forecast":false}
It turns out that it is not possible to update multiple jsons objects within a json array in a single operation by default.
To be able to do that a Postgres function needs to be created:
-- the params are the same as in aforementioned `jsonb_set`
CREATE OR REPLACE FUNCTION update_json_array_elements(target jsonb, path text[], new_value jsonb)
RETURNS jsonb language sql AS $$
-- aggregate the jsonb from parts created in LATERAL
SELECT jsonb_agg(updated_jsonb)
-- split the target array to individual objects...
FROM jsonb_array_elements(target) individual_object,
-- operate on each object and apply jsonb_set to it. The results are aggregated in SELECT
LATERAL jsonb_set(individual_object, path, new_value) updated_jsonb
$$;
The above function was suggested by kubak in this answer: https://stackoverflow.com/a/53712268/782390
Combined with this query:
SELECT
dividends
FROM
stock_financial AS f
INNER JOIN instrument AS i ON i.id = f.instrument_id,
jsonb_array_elements(
update_json_array_elements(f.dividend_forecast->'dividends', '{forecast}', 'true') ||
update_json_array_elements(f.dividend_actual->'dividends', '{forecast}', 'false')
) AS dividends
WHERE
i.symbol = 'ASX_CBA'
ORDER BY
dividends ->>'balanceDate' DESC;
I then get the following output, that it is exactly what I need:
{"amount":"3.1845","forecast":true,"balanceDate":"2023-06-01T00:00:00Z"}
{"amount":"3.0452","forecast":true,"balanceDate":"2022-06-01T00:00:00Z"}
{"amount":"2.3035","forecast":true,"balanceDate":"2021-06-01T00:00:00Z"}
{"amount":"2.9800","forecast":false,"balanceDate":"2020-06-30T00:00:00Z"}
{"amount":"4.3100","forecast":false,"balanceDate":"2019-06-30T00:00:00Z"}
In my postgres database I have json that looks similar to this:
{
"myArray": [
{
"myValue": 1
},
{
"myValue": 2
},
{
"myValue": 3
}
]
}
Now I want to rename myValue to otherValue. I can't be sure about the length of the array! Preferably I would like to use something like set_jsonb with a wildcard as the array index, but that does not seem to be supported. So what is the nicest solution?
You have to decompose a whole jsonb object, modify individual elements and build the object back.
The custom function will be helpful:
create or replace function jsonb_change_keys_in_array(arr jsonb, old_key text, new_key text)
returns jsonb language sql as $$
select jsonb_agg(case
when value->old_key is null then value
else value- old_key || jsonb_build_object(new_key, value->old_key)
end)
from jsonb_array_elements(arr)
$$;
Use:
with my_table (id, data) as (
values(1,
'{
"myArray": [
{
"myValue": 1
},
{
"myValue": 2
},
{
"myValue": 3
}
]
}'::jsonb)
)
select
id,
jsonb_build_object(
'myArray',
jsonb_change_keys_in_array(data->'myArray', 'myValue', 'otherValue')
)
from my_table;
id | jsonb_build_object
----+------------------------------------------------------------------------
1 | {"myArray": [{"otherValue": 1}, {"otherValue": 2}, {"otherValue": 3}]}
(1 row)
Using json functions are definitely the most elegant, but you can get by on using character replacement. Cast the json(b) as text, perform the replace, then change it back to json(b). In this example I included the quotes and colon to help the text replace target the json keys without conflict with values.
CREATE TABLE mytable ( id INT, data JSONB );
INSERT INTO mytable VALUES (1, '{"myArray": [{"myValue": 1},{"myValue": 2},{"myValue": 3}]}');
INSERT INTO mytable VALUES (2, '{"myArray": [{"myValue": 4},{"myValue": 5},{"myValue": 6}]}');
SELECT * FROM mytable;
UPDATE mytable
SET data = REPLACE(data :: TEXT, '"myValue":', '"otherValue":') :: JSONB;
SELECT * FROM mytable;
http://sqlfiddle.com/#!17/1c28a/9/4
i want to update data in bulk i have over 50 rows to be updated in an array of objects in node JS. something like
https://github.com/felixge/node-mysql
and How do I do a bulk insert in mySQL using node.js
var updateData=[
{a: '15',b: 1,c: '24',d: 9,e: 1,f: 0,g: 0,h: 5850,i: 78 },
{a: '12',b: 1,c: '21',d: 9,e: 1,f: 0,g: 0,h: 55,i: 78 },
{a: '13',b: 1,c: '34',d: 9,e: 1,f: 0,g: 0,h: 58,i: 78 },
{a: '14',b: 1,c: '45',d: 9,e: 1,f: 0,g: 0,h: 585,i:78 },
{a: '16',b: 1,c: '49',d: 9,e: 1,f: 0,g: 0,h: 85,i: 78 }
]
my query is : update table set a= updateData.a ,b= updateData.b ,c = updateData.c , d==updateData.d ,e=updateData.e,f=updateData.f where e=updateData.e
As of I know, there is no direct way to do bulk update records in mySQL. But there is a work around for this - You could execute multiple insert statements and then execute the query to achieve the desired result.
To do this, while creating a connection allow it to execute multiple statements as it is disabled by default.
var connection = mysql.createConnection({
host : dbConfig.host,
user : dbConfig.user,
password : dbConfig.password,
database : dbConfig.database,
multipleStatements: true
});
Then, construct the bulk update query in the below syntax by manipulating the inputs you have.
Query1; Query2; Query3;
Say, for Instance,
update table set a='15', b=1, c='24', d=9, e=1, f=0, g=0, h=5850, i=78;update table set a='12', b=1, c='21', d=9, e=1, f=0, g=0, h=5850, i=78;
Then, execute the query as usual,
connection.query(sqlQuery, params, callback);
Hope this helps.
You can accomplish this by enabling the multiple statements feature in your mysql connection. Then you can loop through your updateData and construct mysql statements separated by a ';'. You can see an example of this in this answer.
It's really not easy to bulk update data using node-MySQL but here you can do an alternative if you can use .map function in the frontend. Let me show you what i did with mine--
just make a single update API and use it like this in your frontend-
updateData.map((item, key)=>{
return (
axios.path('/api/update', {
a: item.a,
b: item.b,
c: item.c
})
).then(()=> console.log('updated'))
.catch((err)=> console.log(err))
})
there are couple ifs, but
if you have a unique constraint on column e
if you have a default values for all columns in the target table which are not affected by this query
then you can use this slightly nasty way:
const sql = `insert into table (a,b,c,d,e,f)
values ?
on duplicate key update
a = values(a),
b = values(b),
c = values(c),
d = values(d),
f = values(f)`
the use the query variant with passed values (updateData in your case):
connection.query(sqlString, updateData, callback)
your updateData should be an array of arrays of values to go into a,b,c,d,e,f columns
A little late answering, but using MySQL JSON_TABLE can help. Here's a working example:
UPDATE person a
INNER JOIN (
SELECT
personId, addressType, addressId
FROM JSON_TABLE('
[
{"personId": 318, "addressType": "Primary", "addressId": 712},
{"personId": 319, "addressType": "Shipping", "addressId": 712}
]',
'$[*]' COLUMNS(
personId INT PATH '$.personId',
addressType VARCHAR(10) path '$.addressType',
addressId INT path '$.addressId')
) a) b
ON a.personId = b.personId
SET
a.addressId = b.addressId,
a.addressType = b.addressType;