Using JSON_ARRAY_APPEND to insert large array into JSON document - mysql

I am using JSON_ARRAY_APPEND to update an array object in an existing entry in the table.
I am doing it via code and query looks as below:
UPDATE table t1
SET t1.value = JSON_ARRAY_APPEND('[]', '$', JSON_OBJECT('desc', '${desc}',
'hosts', '[${hosts}]'))
WHERE t1.key = '${key1}
My resulting value looks as below:
[{"desc": "this is desc", "hosts": "[host1,host2, host3]"}]
Desired Output for value is:
[{"desc": "this is desc", "hosts": ["host1","host2","host3"]"}]

You don't need to use JSON_ARRAY_APPEND() since you are creating a new array.
Use JSON_ARRAY(). This ensures the array will be formatted as valid JSON. You should try to avoid writing code that forms JSON by concatenating commas and brackets and quotes, because it's difficult to ensure this will always produce valid JSON format. It's fragile code.
Also, use query parameters instead of trying to interpolate code variables into your SQL statement.
UPDATE table t1
SET t1.value = JSON_ARRAY(
JSON_OBJECT(
'desc', ?,
'hosts', JSON_ARRAY(?, ?, ?, ?))
WHERE t1.key = ?
You'll need one ? parameter marker for each host you want in your hosts array.

``
SELECT CONCAT('"', REPLACE('${hosts}', ',', '","'), '"');
UPDATE table t1
SET t1.value = JSON_ARRAY_APPEND('[]', '$', JSON_OBJECT('desc', '${desc}', 'hosts', '[${hosts}]'))
WHERE t1.key = '${key1};
SET #host = 'host1,host2, host3';
UPDATE table t1
SET t1.value = JSON_ARRAY(
JSON_OBJECT(
'desc', 'this is desc',
'hosts',
JSON_ARRAY(
SUBSTRING_INDEX(#host, ',', 1),
SUBSTRING_INDEX(SUBSTRING_INDEX(#host, ',', 2), ',', -1) ,
SUBSTRING_INDEX(SUBSTRING_INDEX(#host, ',', 3), ',', -1)))) ;
''

Related

How to update json property of TimeStamp in oracle

I have one of the column audit_info with JSON data in the table:
{
"AddInfo":{
"UPN":"abc#abc.com",
"UserName":"abc#abc.com",
"TimeStamp":"2021-10-11T15:54:34:4805634Z",
"Source":"xyz"
},
"ChangeInfo":{
"UPN":"abc#abc.com",
"UserName":"abc#abc.com",
"TimeStamp":"2021-10-11T15:54:34:4832421Z",
"Source":"xyz"
}
}
I need to update TimeStamp of seconds decimal point from :4832421Z to .4832421Z
Can anyone please help me?
update the_table
set audit_info=??
Original Question:
If you do not have {:} anywhere else then you can use replace:
UPDATE table_name
SET audit_info = REPLACE(audit_info, '{:}', '{.}');
fiddle
Updated Question:
On later Oracle versions, if you want to update the last : to . in the paths $.AddInfo.TimeStamp and $.ChangeInfo.TimeStamp then you can use use JSON_TABLE to extract the timestamps and then simple string functions to extract the components before and after the last : and then use JSON_MERGEPATCH to update the specific paths:
MERGE INTO table_name dst
USING (
SELECT t.ROWID AS rid,
JSON_OBJECT(
KEY 'AddInfo' VALUE JSON_OBJECT(
KEY 'TimeStamp'
VALUE SUBSTR(addinfo_ts, 1, INSTR(addinfo_ts, ':', -1) - 1)
|| '.' || SUBSTR(addinfo_ts, INSTR(addinfo_ts, ':', -1) + 1)
),
KEY 'ChangeInfo' VALUE JSON_OBJECT(
KEY 'TimeStamp'
VALUE SUBSTR(changeinfo_ts, 1, INSTR(changeinfo_ts, ':', -1) - 1)
|| '.' || SUBSTR(changeinfo_ts, INSTR(changeinfo_ts, ':', -1) + 1)
)
) AS patch
FROM table_name t
CROSS APPLY JSON_TABLE(
t.audit_info,
'$'
COLUMNS
addinfo_ts VARCHAR2(30) PATH '$.AddInfo.TimeStamp',
changeinfo_ts VARCHAR2(30) PATH '$.ChangeInfo.TimeStamp'
) j
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE
SET audit_info = JSON_MERGEPATCH(audit_info, src.patch);
Then, for the sample data, after the MERGE the table contains:
AUDIT_INFO
{"AddInfo":{"UPN":"abc#abc.com","UserName":"abc#abc.com","TimeStamp":"2021-10-11T15:54:34.4805634Z","Source":"xyz"},"ChangeInfo":{"UPN":"abc#abc.com","UserName":"abc#abc.com","TimeStamp":"2021-10-11T15:54:34.4832421Z","Source":"xyz"}}
If you do not want to worry about specific paths then you can use a regular expression to match the timestamp:
UPDATE table_name
SET audit_info = REGEXP_REPLACE(
audit_info,
'("TimeStamp"\s*:\s*"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}):(\d+Z")',
'\1.\2'
);
fiddle
Try replace. This replace the json field an text and then you convert it again an json. It should be something like this:
update [name_of_the_table]
set audit_info = replace(audit_info::TEXT, ':', '.')::jsonb
where
....

Add a property to every object in a json array in sql sever

I need to add "Description" property with "" value to all items in my json array.
I have tried :
JSON_MODIFY(ReasonCodes, '$[0].Description', '')
and getting result as:
[
{"Name":"jhfghgh","Code":"89798","Note":"dfgbcbxcbx","Description":""},
{"Name":"test7889","Code":"9787","Note":""}
]
basically i want that properties should be also in 2nd or any number of array as well of that json object.
The function JSON_MODIFY() doesn't support wild cards for value of the path parameter, so if the input JSON has a variable structure, you may try to parse the ReasonCodes JSON array with OPENJSON() and default schema, modify each item and aggregate the rows to build the final ouptut:
Table:
CREATE TABLE PD (ReasonCodes varchar(1000))
INSERT INTO PD (ReasonCodes)
VALUES ('[
{"Name":"test1","Code":"0001","Note":"dfgbcbxcbx","Description":null},
{"Name":"test2","Code":"0002","Note":"dfgbcbxcbx","Description":"ABCD"},
{"Name":"test3","Code":"0003","Note":""}
]')
Statement:
UPDATE PD
SET ReasonCodes = CONCAT(
'[',
(
SELECT STRING_AGG(JSON_MODIFY([value], '$.Description', ''), ',')
FROM OPENJSON(ReasonCodes)
),
']'
)
If you need to change the $.Description key, but only when the keys exists, you need a different statement:
UPDATE PD
SET ReasonCodes = CONCAT(
'[',
(
SELECT STRING_AGG(
CASE
WHEN j2.DescriptionCount > 0 THEN JSON_MODIFY(j1.[value], '$.Description', '')
ELSE JSON_QUERY(j1.[value])
END,
','
)
FROM OPENJSON(ReasonCodes) j1
OUTER APPLY (
SELECT COUNT(*)
FROM OPENJSON(j1.[value])
WHERE [key] = 'Description'
) j2 (DescriptionCount)
),
']'
)

How to Update a Specific Object in a JSON Array in Mysql 5.7

How can I update an object in an array based on a unique value in the object?
Let's say this is my json object stored in a table called objects and in a column called content
table: objects
id: 7383
content: { data:[{id: 111, active: 1 }, {id: 222, active: 1 }, {id: 333, active: 0 }] }
I can update objects if I know the position of the element in the array with
SET content = JSON_REPLACE(content,'$.data[1].active', 0)
Where id = 7383
However, if I don't know the position of the array, but I do know the value of id (for example 222) in the object, how can I update active to 0 for the object that has id: 222 ?
Currently, it's complicated to look up numerical values with MySQL JSON functions. In a JSON like the following, it would be simple:
{"id": "222", "active": 1}
There are many ways to get what you need, I present one that can give you ideas (modify everything that is necessary):
UPDATE `objects`
SET `objects`.`content` =
JSON_REPLACE(`objects`.`content`, CONCAT('$.data',
(SELECT
JSON_UNQUOTE(
REPLACE(
JSON_SEARCH(
REPLACE(
REPLACE(
REPLACE(
`der`.`content` ->> '$.data[*].id',
', ',
'","'),
']',
'"]'),
'[',
'["'),
'one',
'222'),
'$',
'')
)
FROM (SELECT `objects`.`content`
FROM `objects`
WHERE `objects`.`id` = 7383) `der`
), '.active'), 0)
WHERE `objects`.`id` = 7383;
Beware of possible performance problems.
See dbfiddle.
In the most recent version of MySQL (>= 8.0.4), the sentence would be much simpler:
UPDATE `objects`
INNER JOIN JSON_TABLE(
`objects`.`content`,
'$.data[*]' COLUMNS(
`rowid` FOR ORDINALITY,
`id` INT PATH '$.id'
)
) `der` ON `der`.`id` = 222
SET `objects`.`content` =
JSON_REPLACE(
`objects`.`content`,
CONCAT('$.data[', `der`.`rowid` - 1, '].active'),
0)
WHERE
`objects`.`id` = 7383;
See db-fiddle.
It can be achieved by combining the functions JSON_SEARCH, which returns a dirty json path to the item you need, and then, extract the value of the jsonpath with an array index, concatenate it with subpath we want to update and use JSON_SET to set a new value to the final json path (tested with MySQL 5.7.32):
-- INPUT ------------------------------------------------
-- unique value for an object in the array
SET #unique_value = "12345";
-- object field we want to update
SET #field_to_update = '.myField';
-- new value
SET #new_value = 1;
-- PROCESSING ------------------------------------------
-- Get json path to the item with specified #unique_value
-- RESULT: $.data[6].id
SET #temp_path = ( TRIM(BOTH '"' FROM ( SELECT JSON_SEARCH(json, 'one', #unique_value, NULL, "$.data")
FROM `my-table`
WHERE `column1` = "abcd" ) ));
-- We are looking for the bracket that delimits index within the array of documents: [11]
SET #closing_bracket_index = (SELECT LOCATE(']', #temp_path));
-- Get json path with index of an object for #unique_value
-- in MySQL, string indexing starts from position 1, not a zero
-- RESULT: $.data[6]
SET #item_path = ( SELECT SUBSTRING(#temp_path, 1, #closing_bracket_index) );
-- $.data[6].myFIeld
SET #item_path_to_update = ( SELECT CONCAT(#item_path, #field_to_update) );
-- UPDATE JSON STATEMENT
UPDATE `my-table`
SET json = JSON_SET(json-column, #item_path_to_update, #new_value)
WHERE `column1` = "abcd";

Generate a JSON string containing the differences in two other JSON strings using T-SQL

Say I have two JSON strings as follows:
[{"RowId":102787,"UserId":1,"Activity":"This is another test","Timestamp":"2017-11-25T14:37:30.3700000"}]
[{"RowId":102787,"UserId":2,"Activity":"Testing the Update function","Timestamp":"2017-11-25T14:37:30.3700000"}]
Both have the same properties but two of the properties in the second string have different values than the first (UserId and Activity). Is it possible, in Azure SQL Database T-SQL, to generate a third JSON string that contains the values in the second string that are different from the first? In other words, I'd like a string returned that looks like this:
[{"UserId":2,"Activity":"Testing the Update function"}]
Also, the solution should assume that the properties in the JSON strings are not known. I need this to be a generic solution for any two JSON strings.
Have not tried this on Azure, but it seems to work on SQL Server 2017
There is probably a more elegant way to get to the final JSON string other than through string manipulation, perhaps we can update the answer as better ways are found.
-- Expected : [{"UserId":2,"Activity":"Testing the Update function"}]
DECLARE #jsonA NVARCHAR(MAX) = '[{"RowId":102787,"UserId":1,"Activity":"This is another test","Timestamp":"2017-11-25T14:37:30.3700000"}]'
,#jsonB NVARCHAR(MAX) = '[{"RowId":102787,"UserId":2,"Activity":"Testing the Update function","Timestamp":"2017-11-25T14:37:30.3700000"}]'
,#result NVARCHAR(MAX) = ''
SELECT #jsonA = REPLACE(REPLACE(#jsonA, ']', ''), '[', '')
,#jsonB = REPLACE(REPLACE(#jsonB, ']', ''), '[', '')
;WITH DSA AS
(
SELECT *
FROM OPENJSON(#jsonA)
)
,DSB AS
(
SELECT *
FROM OPENJSON(#jsonB)
)
SELECT #result += CONCAT (
'"', B.[key], '":'
,IIF(B.[type] = 2, B.[value], CONCAT('"', B.[value], '"')) -- havent checked types other than 1 and 2; think there's a bool type?
,','
)
FROM DSA A
JOIN DSB B ON A.[key] = B.[key]
WHERE A.[value] != B.[value]
SELECT CONCAT('[{', LEFT(#result, LEN(#result) - 1), '}]')

MySQL: Query for list of available options for SET

In particular table there exists a field of SET type with specific legal values:
personType SET('CUSTOMER','SUPPLIER','EMPLOYEE', 'CONTRACTOR') NOT NULL
Is there any way to query MySQL to get a list of the valid values? In the MySQL interpreter I would just run DESCRIBE someTable; however if there is a more direct method that one could use programmatically without lots of parsing it would be nice.
Thanks.
Now, this simply freaks out, but it is MySQL-only and it works!
SELECT TRIM("'" FROM SUBSTRING_INDEX(SUBSTRING_INDEX(
(SELECT TRIM(')' FROM SUBSTR(column_type, 5)) FROM information_schema.columns
WHERE table_name = 'some_table' AND column_name = 'some_column'),
',', #r:=#r+1), ',', -1)) AS item
FROM (SELECT #r:=0) deriv1,
(SELECT ID FROM information_schema.COLLATIONS) deriv2
HAVING #r <=
(SELECT LENGTH(column_type) - LENGTH(REPLACE(column_type, ',', ''))
FROM information_schema.columns
WHERE table_name = 'some_table' AND column_name = 'some_column');
Just replace "some_table" and "some_column" for your specific table/column, and see the magic!
You will see a weird usage of "information_schema.COLLATIONS" - this is because we need a table there - any table - containing at least N rows, where N is the number of elements in your set.
SELECT
column_type
FROM
information_schema.columns
WHERE
table_name = 'some_table'
AND
column_name = 'some_column';
Returns:
column_type
------------------
set('this','that')
The function below returns an array containing all available options for SET with some parsing but not "lots of parsing"... :)
function get_set_values($table_name, $field_name)
{
$sql = 'DESCRIBE ' . $table_name . ' ' . $field_name;
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
return str_getcsv( trim( substr( $row['Type'], 3 ), '()' ), ',', "'" );
}
Remember that in a set column you may have a combination of values or even an empty value (these are also valid).