The result from JSON_SEARCH() is not a valid JSON path. It's quoted, like a JSON string value. Notice the double-quotes:
mysql> select json_search(config, 'one', 'Infrastructure')
from table_config;
+----------------------------------------------+
| json_search(config, 'one', 'Infrastructure') |
+----------------------------------------------+
| "$.deploy[0]" |
+----------------------------------------------+
So if you try to use it as the path argument in JSON_SET(), it doesn't work:
mysql> select json_set(config, json_search(config, 'one', 'Infrastructure'), 'Infrastructure')
from table_config;
ERROR 3143 (42000): Invalid JSON path expression. The error is around character position 1.
To use this as a JSON path, you have to remove those quotes:
mysql> select json_unquote(json_search(config, 'one', 'Infrastructure'))
from table_config;
+------------------------------------------------------------+
| json_unquote(json_search(config, 'one', 'Infrastructure')) |
+------------------------------------------------------------+
| $.deploy[0] |
+------------------------------------------------------------+
Then you can use it in a call to JSON_SET():
mysql> select json_set(config, json_unquote(json_search(config, 'one', 'Infrastructure')), 'Infrastructure')
from table_config;
+------------------------------------------------------------------------------------------------+
| json_set(config, json_unquote(json_search(config, 'one', 'Infrastructure')), 'Infrastructure') |
+------------------------------------------------------------------------------------------------+
| {"deploy": ["Infrastructure", "API Security"], "operate": ["Pen Testing", "Bug Bounty"]} |
+------------------------------------------------------------------------------------------------+
Answer from Bill Karwin on Stack ExchangeThe functions you refer to all work exactly as expected and described in the manual; that is to say JSON_SET will insert or replace if a value already exists, JSON_INSERT will insert if a value doesn't already exist, and JSON_REPLACE will replace a pre-existing value. You can use JSON_ARRAY_INSERT and JSON_ARRAY_APPEND to more easily add values to a JSON array.
-- extract second element
select json_extract('["A", "B", "C"]', '$[1]')
-- "B"
-- replace second element
select json_set('["A", "B", "C"]', '$[1]', 'D')
-- ["A", "D", "C"]
-- insert fourth element
select json_set('["A", "B", "C"]', '$[3]', 'E')
-- ["A", "B", "C", "E"]
-- attempt to insert second element fails as it already exists
select json_insert('["A", "B", "C"]', '$[1]', 'F')
-- ["A", "B", "C"]
-- use json_array_insert to insert a new second element and move the other elements right
select json_array_insert('["A", "B", "C"]', '$[1]', 'F')
-- ["A", "F", "B", "C"]
-- insert fourth element
select json_insert('["A", "B", "C"]', '$[3]', 'F')
-- ["A", "B", "C", "F"]
-- or use json_array_append to add an element at the end
select json_array_append('["A", "B", "C"]', '$', 'F')
-- ["A", "B", "C", "F"]
-- replace second element
select json_replace('["A", "B", "C"]', '$[1]', 'G')
-- ["A", "G", "C"]
-- attempt to replace non-existing element fails
select json_replace('["A", "B", "C"]', '$[3]', 'G')
-- ["A", "B", "C"]
Demo on dbfiddle
To use these functions on a column in a table, simply replace the ["A", "B", "C"] in the above calls with the column name, for example:
create table test (j json);
insert into test values ('["A", "B", "C"]');
select json_array_insert(j, '$[1]', 'F')
from test
-- ["A", "F", "B", "C"]
Demo on dbfiddle
i think found the solution
for json array, it's not possible to use JSON_EXTRACT, JSON_SET, JSON_REPLACE, JSON_INSERT by array values, and you have to know the place of each value in json array (in my opinion it's a weakness).
for example to select 2nd value you can use $[1],
but for insert values you can use
JSON_ARRAY_APPENDand JSON_ARRAY_INSERT
You may use the next procedure:
CREATE PROCEDURE quote_value(max_amount INT)
BEGIN
REPEAT
UPDATE test
SET settings = JSON_REPLACE(settings, CONCAT('$[', max_amount, '].value'), CAST(JSON_UNQUOTE(JSON_EXTRACT(settings, CONCAT('$[', max_amount, '].value'))) AS CHAR));
SET max_amount = max_amount - 1;
UNTIL max_amount < 0 END REPEAT;
END
max_amount parameter defines the amount of objects in the array to be updated (do not forget that the array elements are counted from zero). So set it to max objects per array amount value.
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=166f43d44e57b62da034bd9530713beb
This is under assumption that there are no spaces between the characters in json string, simple but data needs to be verified for this.
update tablename
set settings = replace(settings, '"value\":' , '"value":\"')
where settings not like '%"value":"%'
update tablename
set settings = replace(settings, ',"' , '","')
where settings not like '%","%'
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";