Mysql - update + insert json - mysql

I got a table that has a JSON field. The default value for the field is "NULL" - now I'd like to update a single field of the JSON data.
| ----------------- |
| [int] | [JSON] |
| xy | ipdata |
| ----------------- |
So the field could be something like this:
{ ip: "233.233.233.233", "data": "test", "name": "Peterson", "full_name": "Hanson Peterson" }
So I'd like to update the IP.
update table set ipdata = JSON_SET(ipdata, "$.ip", "newIp") where xy = 2;
But what happens if the field is NULL? The query above does not seems to "create" a new JSON with just the field IP. It just does nothing.
How can I tell mySql to insert the {"ip": "newIp"} if the field is empty and otherwise just update the ip json key?

You can use Case .. When to handle Null. When the field is null, you can instead create Json_object() and set it:
UPDATE table
SET ipdata = CASE WHEN ipdata IS NULL THEN JSON_OBJECT("ip", "newIp")
ELSE JSON_SET(ipdata, "$.ip", "newIp")
END
WHERE xy = "xy";

Related

How do you add a new object to an existing JSON object in MariaDB?

I have a JSON field with an object in it that contains multiple sub objects. The table looks like this:
+---------+----------------------------------------------------------------+
|store_num| fruit_stock |
+---------+----------------------------------------------------------------+
| AL258 | '{"fruits":{"apple":67,"banana":91,"plum":53}}' |
+---------+----------------------------------------------------------------+
| OR419 | '{"fruits":{"apple":109,"banana":44,"plum":98}}' |
+---------+----------------------------------------------------------------+
I want to add an object {"mango":45} to the "AL258" store using prepared statement. I came accross some issues doing this. First was adding an object to another object was not as straight forward as I thought it would be. It turns out I had to create the mango object using the JSON_OBJECT() funtion to start with so:
JSON_OBJECT("mango", 45)
'{"mango":45}'
I then had to get the contents of the "fruits" object so I had to use the JSON_QUERY() function for that:
JSON_QUERY(fruit_stock, '$.fruits')
'{"apple":67, "banana":91, "plum":53}'
Then had to merge the new mango object and the contents of the fruits object. Since I want to replace the field I'm inserting if it already exists I needed to use the JSON_MERGE_PATCH() function:
JSON_MERGE_PATCH(
JSON_QUERY(fruit_stock, '$.fruits'), -- the contents of fruit_stock: '{"apple":67,"banana":91,"plum":53}'
JSON_OBJECT("mango", 45) -- the new mango object: '{"mango":45}'
)
Now that I have the naked object fields '{"apple":67, "banana":91, "plum":53}', and '{"mango":45}' I needed to combine them into the "fruits" object. To do this I needed to create an entirely new "fruits" object using the JSON_OBJECT() function:
JSON_OBJECT(
'fruits', -- the new fruits object
JSON_MERGE_PATCH( -- the contents of fruit_stock
JSON_QUERY(fruit_stock, '$.fruits'), -- the new mango object
JSON_OBJECT("mango", 45)
)
)
'{"fruits":{"apple":67, "banana":91, "plum":53, "mango":45}}'
Adding in a WHERE clause to select the store...
UPDATE store_table SET fruit_stock =
JSON_OBJECT(
'fruits',
JSON_MERGE_PATCH(
JSON_QUERY(fruit_stock, '$.fruits'),
JSON_OBJECT("mango", 45)
)
)
WHERE HEX(store) = 'AL258';
Results in the following table:
+---------+----------------------------------------------------------------+
|store_num| fruit_stock |
+---------+----------------------------------------------------------------+
| AL258 | '{"fruits":{"apple":67,"banana":91,"plum":53,"mango":45}}' |
+---------+----------------------------------------------------------------+
| OR419 | '{"fruits":{"apple":109,"banana":44,"plum":98}}' |
+---------+----------------------------------------------------------------+
My question: Is this the best way to do this, or is there a more efficient and/or readable option using MariaDB?
Just use the similar syntax as the existing jsons for the values to be added by using JSON_MERGE_PATCH() function :
UPDATE store_table
SET fruit_stock = JSON_MERGE_PATCH(fruit_stock, '{"fruits":{"mango": 45}}')
WHERE store_num = 'AL258';
Demo

How can I iterate a JSON in SQL?

So I have a JSON variable with several values like this:
"["1", "2", "3", "4"]"
What i need to do is pass that value to an SQL procedure to mount a query in which the WHERE clause adds all the values in the JSON, so something like parsing the JSON, interate it and concat it in order to get an #where similar to:
AND id=1 AND id=2 AND id=3 AND id=4
I tried something like this, as something really similar is taking place in an already existing procedure, but doesn't work:
SET #idWhere="";
IF id IS NOT NULL AND JSON_EXTRACT(id, '$[0]') IS NOT NULL THEN
SET #idWhere = CONCAT(#idWhere," AND JSON_SEARCH('",id,"','one',id) IS NOT NULL ");
END IF;
Where id is both the name of the JSON and the column name.
Thanks in advance!
If you are running MySQL 8.0, you can use json_table() to turn the array to a recordset, and then aggregate the results with group_concat():
select group_concat(concat('id = ', i) separator ' and ') id_where
from json_table(
'["1", "2", "3", "4"]',
"$[*]" columns(i int path '$')
) as t
In this demo on DB Fiddle, this yields:
| id_where |
| --------------------------------------- |
| id = 1 and id = 2 and id = 3 and id = 4 |
NB: you probably want ors, not ands.

How to conditional update field in JSON in MySQL?

We have Student table which contains JSON in 'jsonData' column(longtext).
We need to change value of "isHandicapped" field in json.
Structure:
{
"data": {
"schoolData": {
"studentListe": [
{
"student": {
"studentId": 111749,
"isHandicapped": false
}
}
],
},
}
}
Old Data: "isHandicapped": false
New Value: "isHandicapped": "NO"
This should be conditional update as for true we need to change value as
Old Data: "isHandicapped": true
New Value: "isHandicapped": "Ja"
Primary field of table is ID.
I got following SQL Query for same but unable to understand how to add conditional update that if value is true put Ja else NO:
update Student
set data = JSON_SET(data, "'$."data"."schoolData"."studentListe"[*]."isHandicapped", "?")
where id = 2;
Here's how you would extract that field:
select json_extract(data, '$.data.schoolData.studentListe[*].student.isHandicapped') from Student;
+-------------------------------------------------------------------------------+
| json_extract(data, '$.data.schoolData.studentListe[*].student.isHandicapped') |
+-------------------------------------------------------------------------------+
| [false] |
+-------------------------------------------------------------------------------+
You can use JSON_SET() to set a value for a specific array member 0:
update student set data = json_set(data, '$.data.schoolData.studentListe[0].student.isHandicapped', 'NO');
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
select json_extract(data, '$.data.schoolData.studentListe[*].student.isHandicapped') from Student;
+-------------------------------------------------------------------------------+
| json_extract(data, '$.data.schoolData.studentListe[*].student.isHandicapped') |
+-------------------------------------------------------------------------------+
| ["NO"] |
+-------------------------------------------------------------------------------+
But you can't use the * wildcard to update all array members:
update student set data = json_set(data, '$.data.schoolData.studentListe[*].student.isHandicapped', '"NO"');
ERROR 3149 (42000): In this situation, path expressions may not contain the * and ** tokens or an array range.
I see a lot of questions on Stack Overflow these days about selecting or modifying JSON data in MySQL. Usually the answer is:
You shouldn't store data in JSON format if you need to select or update fields within the JSON document.
This type of task is easy to solve if you stored your data in normal tables and columns:
UPDATE StudentListe
SET isHandicapped = 'NO'
WHERE studentId = 2;
The following update statement will do the trick:
UPDATE Student
SET $.isHandicapped= CASE
WHEN JSON_EXTRACT(data, "'$."data"."schoolData"."studentListe"[*]."isHandicapped")='false'
THEN 'NO'
ELSE 'Ja' end
WHERE id=2;

KEA dhcp Mysql Backend

I have an issue with the new ISC DHCP, KEA, on the MySQL backend.
I want to store leases in my DB, it works but some info are not stored.
I obtain this kind of entry in my DB :
+------------+--------+-----------+----------------+---------------------+-----------+----------+----------+----------+
| address | hwaddr | client_id | valid_lifetime | expire | subnet_id | fqdn_fwd | fqdn_rev | hostname |
+------------+--------+-----------+----------------+---------------------+-----------+----------+----------+----------+
| 3232236052 | '° | NULL | 4000 | 2015-07-22 08:54:32 | 1 | 0 | 0 | │
+------------+--------+-----------+----------------+---------------------+-----------+----------+----------+----------+
The address field is the IP adress in decimal, I checked and it's the good one.
I didn't find how change the IP adress to IPv4 format and how store mac address in the hwaddr field in the KEA documentation.
If someone know how to do this I will be really grateful !
Thank you !
As per KEA documentation hwaddr field is VARBINARY. You should be able to see the value IP address and hwaddr using:
SELECT INET_NTOA(address), HEX(hwaddr), lease4.* FROM lease4;
I had a similar need to create KEA host reservations and populate the MAC and IP addresses as regular strings while still having the fields KEA uses updated automatically on an INSERT or UPDATE.
What I ended up doing is creating two new fields that would hold those string values ('hosts.dhcp_identifier_str' and 'hosts.ipv4_address_str'):
ALTER TABLE `hosts` ADD `dhcp_identifier_str` VARCHAR(12) NOT NULL AFTER `dhcp_identifier`;
ALTER TABLE `hosts` ADD `ipv4_address_str` VARCHAR(15) NULL DEFAULT NULL AFTER `ipv4_address`;
Then, I keep the corresponding fields that KEA uses ('hosts.dhcp_identifier' and 'hosts.ipv4_address') up to date by using BEFORE INSERT/UPDATE MySQL triggers:
DELIMITER //
DROP TRIGGER IF EXISTS `host_BINS`//
CREATE TRIGGER `host_BINS` BEFORE INSERT ON `hosts`
FOR EACH ROW BEGIN
IF (NEW.dhcp_identifier = '' AND NEW.dhcp_identifier_str != '') THEN
SET NEW.dhcp_identifier = UNHEX(UPPER(NEW.dhcp_identifier_str));
ELSEIF (NEW.dhcp_identifier_str = '' AND NEW.dhcp_identifier != '') THEN
SET NEW.dhcp_identifier_str = LOWER(HEX(NEW.dhcp_identifier));
END IF;
IF (NEW.ipv4_address IS NULL AND NEW.ipv4_address_str IS NOT NULL) THEN
SET NEW.ipv4_address = INET_ATON(NEW.ipv4_address_str);
ELSEIF (NEW.ipv4_address_str IS NULL AND NEW.ipv4_address IS NOT NULL) THEN
SET NEW.ipv4_address_str = CAST(INET_NTOA(NEW.ipv4_address) AS CHAR);
END IF;
END
//
DROP TRIGGER IF EXISTS `host_BUPD`//
CREATE TRIGGER `host_BUPD` BEFORE UPDATE ON `hosts`
FOR EACH ROW BEGIN
IF (NEW.dhcp_identifier_str != '' AND OLD.dhcp_identifier != UNHEX(UPPER(NEW.dhcp_identifier_str))) THEN
SET NEW.dhcp_identifier = UNHEX(UPPER(NEW.dhcp_identifier_str));
ELSEIF (NEW.dhcp_identifier != '' AND OLD.dhcp_identifier_str != LOWER(HEX(NEW.dhcp_identifier))) THEN
SET NEW.dhcp_identifier_str = LOWER(HEX(NEW.dhcp_identifier));
END IF;
IF (NEW.ipv4_address_str IS NOT NULL AND OLD.ipv4_address != INET_ATON(NEW.ipv4_address_str)) THEN
SET NEW.ipv4_address = INET_ATON(NEW.ipv4_address_str);
ELSEIF (NEW.ipv4_address IS NOT NULL AND OLD.ipv4_address_str != CAST(INET_NTOA(NEW.ipv4_address) AS CHAR)) THEN
SET NEW.ipv4_address_str = CAST(INET_NTOA(NEW.ipv4_address) AS CHAR);
END IF;
END
//
This works whether you INSERT/UPDATE an entry using the dhcp_identifier/ipv4_address or dhcp_identifier_str/ipv4_address_str pairs.
I'm sure you can use the same triggers for the 'lease4' table.
Hope that helps.

SQL query to remove certain text from each field in a specific column?

I recently recoded one of my sites, and the database structure is a little bit different.
I'm trying to convert the following:
*----*----------------------------*
| id | file_name |
*----*----------------------------*
| 1 | 1288044935741310953434.jpg |
*----*----------------------------*
| 2 | 1288044935741310352357.rar |
*----*----------------------------*
Into the following:
*----*----------------------------*
| id | file_name |
*----*----------------------------*
| 1 | 1288044935741310953434 |
*----*----------------------------*
| 2 | 1288044935741310352357 |
*----*----------------------------*
I know that I could do a foreach loop with PHP, and explode the file extension off the end, and update each row that way, but that seems like way too many queries for the task.
Is there any SQL query that I could run that would allow me to remove the file exentision from each field in the file_name column?
You can use the REPLACE() function in native MySQL to do a simple string replacement.
UPDATE tbl SET file_name = REPLACE(file_name, '.jpg', '');
UPDATE tbl SET file_name = REPLACE(file_name, '.rar', '');
This should work:
UPDATE MyTable
SET file_name = SUBSTRING(file_name,1, CHAR_LENGTH(file_name)-4)
This will strip off the final extension, if any, from file_name each time it is run. It is agnostic with respect to extension (so you can have ".foo" some day) and won't harm extensionless records.
UPDATE tbl
SET file_name = TRIM(TRAILING CONCAT('.', SUBSTRING_INDEX(file_name, '.', -1) FROM file_name);
You can use SUBSTRING_INDEX function
SUBSTRING_INDEX(str,delim,count)
Where str is the string, delim is the delimiter (from which you want a substring to the left or right of), and count specifies which delimiter (in the event there are multiple occurrences of the delimiter in the string)
Example:
UPDATE table SET file_name = SUBSTRING_INDEX(file_name , '.' , 1);