How to conditional update field in JSON in MySQL? - 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;

Related

How do I change an array-valued key of a json field in a MYSQL database using JSON_REPLACE and JSON_ARRAY?

I'm trying to change one of the array-valued keys (called religions) of a JSON field named preferences in a MySQL table.
I'm not using JSON_ARRAY_APPEND because the changes in the field can be arbitrary and can involve both removals and additions.
Right now I'm trying to change it by using the following query.
const religionPreferences = ["Buddhism","Christianity","Non religious"]
const sql = `UPDATE users SET preferences = JSON_REPLACE(
preferences,
'$.${key}',
JSON_ARRAY(religionPreferences[0], religionPreferences[1], religionPreferences[2])
)
WHERE id = "${req.params.id}" LIMIT 1`
The problem is that the religionPreferences array can have anywhere from 0-14 elements and I wasn't sure how to proceed with variable arguments having to be passed into the JSON_ARRAY function.
const religionPreferences = ["Buddhism","Christianity","Non religious"]
const sql = `UPDATE users SET preferences = JSON_REPLACE(
preferences,
'$.religions',
JSON_ARRAY(religionPreferences[0], religionPreferences[1], religionPreferences[2])
)
WHERE id = "${req.params.id}" LIMIT 1`
Another approach I tried was just directly setting the argument for JSON_REPLACE as an array literal instead of passing in JSON_ARRAY as below.
const religionPreferences = ["Buddhism","Christianity","Non religious"]
const sql = `UPDATE users SET preferences = JSON_REPLACE(
preferences,
'$.religions',
${religionPreferences}
)
WHERE id = "${req.params.id}" LIMIT 1`
The problem is that I want it to be saved as
religions: ["Christian","Buddhist","Other"]
instead of
religions: '["Christian","Buddhist","Other"]'
But when I tried to save it as an array directly without quotes around it, it said the SQL had an error..
Thanks so much.
-Jay
Here's a demo. First we set up some JSON documents for testing.
mysql> set #j = '{"religions": []}';
Query OK, 0 rows affected (0.00 sec)
mysql> set #r = '["Buddhism","Christianity","Non religious"]';
Query OK, 0 rows affected (0.00 sec)
What you don't want is to treat #r as a string, and set it as the value for the "religions" key. This is the default, because otherwise there would be no way to set a value to a string that happens to contain characters that appear like a JSON document.
mysql> select json_replace(#j, '$.religions', #r);
+--------------------------------------------------------------------+
| json_replace(#j, '$.religions', #r) |
+--------------------------------------------------------------------+
| {"religions": "[\"Buddhism\",\"Christianity\",\"Non religious\"]"} |
+--------------------------------------------------------------------+
1 row in set (0.00 sec)
But if you explicitly cast the value as a JSON document, then you can do what you want. It sets the value for the "religions" key to be a JSON array.
mysql> select json_replace(#j, '$.religions', cast(#r as json));
+--------------------------------------------------------------+
| json_replace(#j, '$.religions', cast(#r as json)) |
+--------------------------------------------------------------+
| {"religions": ["Buddhism", "Christianity", "Non religious"]} |
+--------------------------------------------------------------+

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.

Mysql - update + insert json

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";

SQL How to replace values of select return?

In my database (MySQL) table, has a column with 1 and 0 for represent true and false respectively.
But in SELECT, I need it replace for true or false for printing in a GridView.
How to I make my SELECT query to do this?
In my current table:
id | name | hide
1 | Paul | 1
2 | John | 0
3 | Jessica | 1
I need it show thereby:
id | name | hide
1 | Paul | true
2 | John | false
3 | Jessica | true
You have a number of choices:
Join with a domain table with TRUE, FALSE Boolean value.
Use (as pointed in this answer)
SELECT CASE WHEN hide = 0 THEN FALSE ELSE TRUE END FROM
Or if Boolean is not supported:
SELECT CASE WHEN hide = 0 THEN 'false' ELSE 'true' END FROM
I got the solution
SELECT
CASE status
WHEN 'VS' THEN 'validated by subsidiary'
WHEN 'NA' THEN 'not acceptable'
WHEN 'D' THEN 'delisted'
ELSE 'validated'
END AS STATUS
FROM SUPP_STATUS
This is using the CASE
This is another to manipulate the selected value for more that two options.
You can do something like this:
SELECT id,name, REPLACE(REPLACE(hide,0,"false"),1,"true") AS hide FROM your-table
Hope this can help you.
If you want the column as string values, then:
SELECT id, name, CASE WHEN hide = 0 THEN 'false' ELSE 'true' END AS hide
FROM anonymous_table
If the DBMS supports BOOLEAN, you can use instead:
SELECT id, name, CASE WHEN hide = 0 THEN false ELSE true END AS hide
FROM anonymous_table
That's the same except that the quotes around the names false and true were removed.
You can use casting in the select clause like:
SELECT id, name, CAST(hide AS BOOLEAN) FROM table_name;
I saying that the case statement is wrong but this can be a good solution instead.
If you choose to use the CASE statement, you have to make sure that at least one of the CASE condition is matched. Otherwise, you need to define an error handler to catch the error. Recall that you don’t have to do this with the IF statement.
SELECT if(hide = 0,FALSE,TRUE) col FROM tbl; #for BOOLEAN Value return
or
SELECT if(hide = 0,'FALSE','TRUE') col FROM tbl; #for string Value return
in Postgres 11 I had to do this:
type is an int
SELECT type,
CASE
WHEN type = 1 THEN 'todo'
ELSE 'event'
END as type_s
from calendar_items;
replace the value in select statement itself
(CASE WHEN Mobile LIKE '966%' THEN (select REPLACE(CAST(Mobile AS nvarchar(MAX)),'966','0')) ELSE Mobile END)

How do I select noncontiguous characters from a string of text in MySQL?

I have a table with millions of rows and a single column of text that is exactly 11,159 characters long. It looks like this:
1202012101...(to 11,159 characters)
1202020120...
0121210212...
...
(to millions of rows)
I realize that I can use
SELECT SUBSTR(column,2,4) FROM table;
...if I wanted to pull out characters 2, 3, 4, and 5:
1202012101...
1202020120...
0121210212...
^^^^
But I need to extract noncontiguous characters, e.g. characters 1,5,7:
1202012101...
1202020120...
0121210212...
^ ^ ^
I realize this can be done with a query like:
SELECT CONCAT(SUBSTR(colm,1,1),SUBSTR(colm,5,1),SUBSTR(colm,7,1)) FROM table;
But this query gets very unwieldy to build for thousands of characters that I need to select. So for the first part of the question - how do I build a query that does something like this:
SELECT CHARACTERS(string,1,5,7) FROM table;
Furthermore, the indices of the characters I want to select are from a different table that looks something like this:
char_index keep_or_discard
1 keep
2 discard
3 discard
4 discard
5 keep
7 discard
8 keep
9 discard
10 discard
So for the second part of the question, how could I build a query to select specific characters from the first table based on whether keep_or_discard="keep" for that character's index in the second table?
this function does what you want:
CREATE DEFINER = `root`#`localhost` FUNCTION `test`.`getsubset`(selection mediumtext, longstring mediumtext)
RETURNS varchar(200)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'This function returns a subset of characters.'
BEGIN
SET #res:='';
SET #selection:=selection;
WHILE #selection<>'' DO
set #pos:=CONVERT(#selection, signed);
set #res := concat_ws('',#res,SUBSTRING(longstring,#pos,1));
IF LOCATE(',',#selection)=0 THEN
SET #selection:='';
END IF;
set #selection:=SUBSTRING(#selection,LOCATE(',',#selection)+1);
END WHILE;
RETURN #res;
END
Note: the CONVERT('1,2,3,4',signed) will yield 1, but it will give a warning.
I have it defined to be available in the database test.
The function takes two parameters; a string(!) with a list of positions, and a long string from where you want the characters taken.
An example of using this:
mysql> select * from keepdiscard;
+---------+------------+
| charind | keepordisc |
+---------+------------+
| 1 | keep |
| 2 | discard |
| 3 | keep |
| 4 | discard |
| 5 | keep |
| 6 | keep |
+---------+------------+
6 rows in set (0.00 sec)
mysql> select * from test;
+-------------------+
| longstring |
+-------------------+
| abcdefghijklmnopq |
| 123456789 |
+-------------------+
2 rows in set (0.00 sec)
mysql> select getsubset(group_concat(charind ORDER BY charind),longstring) as result from keepdiscard, test where keepordisc='keep' group by longstring;
+--------+
| result |
+--------+
| 1356 |
| acef |
+--------+
2 rows in set, 6 warnings (0.00 sec)
The warnings stem from the fast conversion to integer that is done in the function. (See comment above)
How about dynamic sql? (You will need to build the select part of the query)
CREATE PROCEDURE example_procedure()
BEGIN
--
--build the concat values here
--
SET #ids := '';
SET #S = 'SELECT #ids := built_concat_of_values FROM table';
PREPARE n_StrSQL FROM #S;
EXECUTE n_StrSQL;
DEALLOCATE PREPARE n_StrSQL;
END
You can write a php script to do this for you:
<?php
//mysql connect
$conn = mysql_connect('localhost', 'mysql_user', 'mysql_password');
if (!$conn) {
echo 'Unable to connect to DB: ' . mysql_error();
exit;
}
//database connect
$db = mysql_select_db('mydb');
if (!$db) {
echo 'Unable to select mydb: ' . mysql_error();
exit;
}
//get the keep numbers you’re going to use.
//and change the number into string so, for example, instead of 5 you get 'SUBSTR(colm,5,1)'
$result = mysql_query("SELECT number FROM number_table WHERE keep_or_discard='keep'");
$numbers = array();
while ($row = mysql_fetch_assoc($result)) {
$row = 'SUBSTR(colm,' . $row . ',1)';
$numbers = $row;
}
//implode the array so you get one long string with all the substrings
//eg. 'SUBSTR(colm,1,1),SUBSTR(colm,5,1),SUBSTR(colm,12,1)'
$numbers = implode(",", $numbers);
//pull the numbers you need and save them to an array.
$result = mysql_query("SELECT " . $numbers . " FROM table");
$concat = array();
while ($row = mysql_fetch_assoc($result)) {
$concat= $row;
}
And there you have an array with the correct numbers.
I'm sorry if you can't/don't want to use PHP for this, I just don't really know how to do this without PHP, Perl, Python or some other similar language. Hopefully this solution will help somehow...
The source of your difficulty is that your schema does not represent the true relationships between the data elements. If you wanted to achieve this with "pure" SQL, you would need a schema more like:
table
ID Index Char
1 0 1
1 1 2
1 2 0
charsToKeep
ID Index Keep
1 0 false
1 1 true
1 2 true
Then, you could perform a query like:
SELECT Char FROM table t JOIN charsToKeep c ON t.ID = c.ID WHERE c.Keep = true
However, you probably have good reasons for structuring your data the way you have (my schema requires much more storage space per character and the processing time is also probably much longer from what I am about to suggest).
Since SQL does not have the tools to understand the schema you have embedded into your table, you will need to add them with a user-defined function. Kevin's example of dynamic SQL may also work, but in my experience this is not as fast as a user-defined function.
I have done this in MS SQL many times, but never in MySql. You basically need a function, written in C or C++, that takes a comma-delimited list of the indexes you want to extract, and the string from which you want to extract them from. Then, the function will return a comma-delimited list of those extracted values. See these links for a good starting point:
http://dev.mysql.com/doc/refman/5.1/en/adding-functions.html
http://dev.mysql.com/doc/refman/5.1/en/adding-udf.html
To build the concatenated list of indexes you want to extract from the char_index table, try the group_concat function:
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
Hope this helps!