Update a field with specific value inside a Json Object with MariaDB

Viewed 599

I'm trying to update the data stored in a json column in MariaDB (libmysql version - 5.6.43 , Server: 10.3.34-MariaDB-cll-lve - MariaDB Server).

My data is structured like this:

ID json_data
1 {....}
2 {....}

where json_data is structured as follows:

{
    "company": {
        "id": "",
        "name": "",
        "address": ""
    },
    "info_company": {
          "diff_v": "1",
          "grav_v": "",
          "diff_s": "2",
          "grav_s": "",
          "diff_g": "3",
          "grav_g": "",
          "diff_ri": "4",
          "grav_ri": "2"
    }
}

I'm trying to update data inside info_company replacing:

  • "1" with "<50%"
  • "2" with "<50%"
  • "3" with ">50%"
  • "4" with ">50%"

so the result should be:

{
    "company": {
        "id": "",
        "name": "",
        "address": ""
    },
    "info_company": {
          "diff_v": "<50%",
          "grav_v": "",
          "diff_s": "<50%",
          "grav_s": "",
          "diff_g": ">50%",
          "grav_g": "",
          "diff_ri": ">50%",
          "grav_ri": "<50%"
    }
}

By writing this query, I can retrieve the info_company data, but then for each key contained I cannot update the data following the new value.

SELECT new_t.id, JSON_EXTRACT(new_t.json_data, “$.info_company“) FROM (SELECT * FROM `my_table` WHERE json_data LIKE “%info_company%”) new_t

Output:

ID json_data
1 {"diff_v": "1","grav_v": "","diff_s": "2","grav_s": "","diff_g": "3","grav_g": "","diff_ri": "4","grav_ri": "2"}

Thank you for your help.

2 Answers

You can solve this problem by using a CTE to generate a regex to match the keys (and desired matching values) inside info_company and then using REGEXP_REPLACE to replace a 1 or 2 with <50% and a 3 or 4 with >50%:

UPDATE my_table
JOIN (
  WITH jkeys_table AS (
    SELECT id, JSON_KEYS(json_data, '$.info_company') AS jkeys
    FROM my_table
  )
  SELECT id,
         CONCAT('((?:',
                REPLACE(SUBSTRING(jkeys, 2, CHAR_LENGTH(jkeys)-2), ', ', '|'),
                ')\\s*:\\s*)"([12])"'
         ) AS regex12,
         CONCAT('((?:',
                REPLACE(SUBSTRING(jkeys, 2, CHAR_LENGTH(jkeys)-2), ', ', '|'),
                ')\\s*:\\s*)"([34])"'
         ) AS regex34
  FROM jkeys_table
) rt ON my_table.id = rt.id
SET json_data = REGEXP_REPLACE(REGEXP_REPLACE(json_data, regex12, '\\1"<50%"'), regex34, '\\1">50%"')

Output (for your sample JSON):

id  json_data
1   {
        "company": 
        {
            "id": "",
            "name": "",
            "address": ""
        },
        "info_company": 
        {
            "diff_v": "<50%",
            "grav_v": "",
            "diff_s": "<50%",
            "grav_s": "",
            "diff_g": ">50%",
            "grav_g": "",
            "diff_ri": ">50%",
            "grav_ri": "<50%"
        }
    }

Demo on dbfiddle

If it's possible the keys in info_company might exist elsewhere inside json_data, you need to localise the changes to the info_company element. You can do this by changing the SET clause of the UPDATE to:

SET json_data = JSON_REPLACE(json_data, '$.info_company',
                JSON_MERGE_PATCH(JSON_QUERY(json_data, '$.info_company'),
                                 REGEXP_REPLACE(REGEXP_REPLACE(JSON_QUERY(json_data, '$.info_company'), regex12, '\\1"<50%"'), regex34, '\\1">50%"')
                                )
                )

Demo on dbfiddle

If the keys in info_company are the same for every row, you can optimise the query by only computing the regex12 and regex34 values once, and then applying those values to all rows in my_table using a CROSS JOIN:

UPDATE my_table
CROSS JOIN (
  WITH jkeys_table AS (
    SELECT JSON_KEYS(json_data, '$.info_company') AS jkeys
    FROM my_table
    LIMIT 1
  )
  SELECT CONCAT('((?:',
                REPLACE(SUBSTRING(jkeys, 2, CHAR_LENGTH(jkeys)-2), ', ', '|'),
                ')\\s*:\\s*)"([12])"'
         ) AS regex12,
         CONCAT('((?:',
                REPLACE(SUBSTRING(jkeys, 2, CHAR_LENGTH(jkeys)-2), ', ', '|'),
                ')\\s*:\\s*)"([34])"'
         ) AS regex34
  FROM jkeys_table
) rt
SET json_data = REGEXP_REPLACE(REGEXP_REPLACE(json_data, regex12, '\\1"<50%"'), regex34, '\\1">50%"')

Demo on dbfiddle

Tested on MariaDB 10.3.34 database server with your json_data:

DELIMITER //
CREATE PROCEDURE percentage()
BEGIN
SELECT @info_keys:=JSON_KEYS(json_data, "$.info_company") FROM my_table;
SELECT @info_keys_num:=JSON_LENGTH(@info_keys);
WHILE @info_keys_num >= 0 DO
   SET @info_keys_num = @info_keys_num - 1;
   SELECT @info_attr:=JSON_EXTRACT(@info_keys, CONCAT("$[", @info_keys_num, "]"));
   UPDATE my_table SET json_data = JSON_REPLACE(json_data, CONCAT("$.info_company.", @info_attr), "<50%") 
      WHERE CHAR_LENGTH(JSON_VALUE(json_data, CONCAT("$.info_company.", @info_attr))) = 1 AND
            JSON_VALUE(json_data, CONCAT("$.info_company.", @info_attr)) < 3;
   UPDATE my_table SET json_data = JSON_REPLACE(json_data, CONCAT("$.info_company.", @info_attr), ">50%") 
      WHERE CHAR_LENGTH(JSON_VALUE(json_data, CONCAT("$.info_company.", @info_attr))) = 1 AND
            JSON_VALUE(json_data, CONCAT("$.info_company.", @info_attr)) > 2;
END WHILE;
END;
//
DELIMITER ;

call percentage();

Example of output:

MariaDB [test]> call percentage();
+------------------------------------------------------------------------------------+
| @info_keys:=JSON_KEYS(json_data, "$.info_company")                                 |
+------------------------------------------------------------------------------------+
| ["diff_v", "grav_v", "diff_s", "grav_s", "diff_g", "grav_g", "diff_ri", "grav_ri"] |
+------------------------------------------------------------------------------------+
1 row in set (0.001 sec)

... [cut here] ...

Query OK, 5 rows affected (0.011 sec)
Related