How can I convert the string values inside a MySQL JSON array to upper case? - mysql

I have a table that contains a JSON column, and in it a JSON array:
mysql> SELECT profile->'$.countriesVisited' from users;
+-------------------------------+
| profile->'$.countriesVisited' |
+-------------------------------+
| ["us", "il"] |
| ["co", "ph"] |
+-------------------------------+
2 rows in set (0.00 sec)
I want to convert the values inside the array into upper case. (I am assuming this answer would also assist lower case, string replacements.. etc.)
I've been trying to use UPPER, JSON_ARRAY, JSON_QUOTE, JSON_UNQUOTE, etc - at best I end up with a string representation of what I want.
How can I do this? I'm running MySQL 5.7.19.

You need to use JSON casting. Try the following:
UPDATE users
SET profile = JSON_SET(
profile,
'$.countriesVisited',
CAST(
UPPER(profile->'$.countriesVisited')
AS JSON
)
);

Related

How to convert a string value of json into dateTime and compare

SELECT JSON_EXTRACT(z2schedule,'$[*].start') as startDate from
cpmdev_z2weekly_schedule
After running above code I am getting response as :-
Now If I tried to compare each value to time value using below code but it is not working:-
SELECT JSON_EXTRACT(z2schedule,'$[*].start') as startDate from
cpmdev_z2weekly_schedule where
JSON_EXTRACT(z2schedule,CONVERT('$[*].start'),'TIME')>
'CONVERT('2022-11-02 13:10:00:000', TIME)
My requirement is only to compare each value with the time value and return only if the value is greater than given time.
For Example in Table I have Data as:-
[{"start":"09:00:00.000","end":"17:00:00.000"}]
[{"start":"10:00:00.000","end":"17:00:00.000"}]
[{"start":"11:00:00.000","end":"17:00:00.000"}]
Now I want all the start Date which is greater then 10:00:00
In above case then it should return :
11:00:00.000
The JSON you show is an array of objects. When you use $[*].start, it returns a JSON array. This is not a single time. You can see the square brackets around the time value:
mysql> set #j = '[{"start":"09:00:00.000","end":"17:00:00.000"}]';
mysql> select json_extract(#j, '$[*].start') as times;
+------------------+
| times |
+------------------+
| ["09:00:00.000"] |
+------------------+
The square brackets make it not valid as a time value.
mysql> select convert(json_extract(#j, '$[*].start'), time) as times;
+-------+
| times |
+-------+
| NULL |
+-------+
Since your JSON array seems to have only one object in it, you could use $[0] to select the first object in the array. Then it returns a single string value and that is convertable to a time:
mysql> select convert(json_extract(#j, '$[0].start'), time) as time;
+----------+
| time |
+----------+
| 09:00:00 |
+----------+
Note also that the data type named in the CONVERT() function is a keyword, not a quoted string. That is, 'time' is incorrect, just use time.
If your JSON array may have more than one object, and you need to test all of them, then you should use the JSON_TABLE() function.
By the way, all these issues would be avoided if you stored your start and end times in normal rows and columns. Using JSON makes many queries more difficult to develop and optimize. You should consider normalizing your data, and not using JSON.

MySQL 5.7 JSON_REMOVE multiple values from array in one query

I have a MySQL 5.7 database with a JSON column containing an array of strings.
I need to remove a varying number of those strings from the array, by value, in one query.
Example JSON:
["1-1-2", "1-1-3", "1-2-2", "1-2-3", "1-1-16", "1-1-17"]
I may need to remove "1-1-16" and "1-1-17" so I end up with:
["1-1-2", "1-1-3", "1-2-2", "1-2-3"]
At other times, I may need to remove just one value, or several more, in one query.
JSON_REMOVE() can accept multiple path arguments, but the problem is that when multiple paths are specified, the result of JSON_REMOVE() is passed sequentially left to right on each path, which makes it very difficult to use the result of JSON_SEARCH() for each passed path.
For example, this does not work, because the 2nd JSON_SEARCH will return the incorrect index for '1-1-17' after '1-1-16' has been removed:
UPDATE json_meta
SET document =
JSON_REMOVE( document,
JSON_UNQUOTE(JSON_SEARCH(document, 'one', '1-1-16')),
JSON_UNQUOTE(JSON_SEARCH(document, 'one', '1-1-17')),
)
WHERE id=10
You need to do this instead:
UPDATE json_meta
SET document =
JSON_REMOVE( document,
JSON_UNQUOTE(JSON_SEARCH(document, 'one', '1-1-16')),
JSON_UNQUOTE(JSON_SEARCH(JSON_REMOVE( document,
JSON_UNQUOTE(JSON_SEARCH(document, 'one', '1-1-16'))), 'one', '1-1-17'))
)
WHERE id=10
The query grows exponentially complex with each additional string that needs to be removed.
I'm wondering if the best solution would just be to use a chained REPLACE() with all permutations of commas on each string (i.e. each string with a comma before, a comma after, a comma both before and after).
Final note: I found another question with the exact same issue described here. However, that question doesn't have an accepted answer, and the one answer there is very complex. That answer indicates that MySQL 5.6 doesn't have much JSON support; I am wondering, since I'm using MySQL 5.7, is there a simpler solution possible?
If you can ensure you search for the items in the right order, you can nest them:
SELECT JSON_REMOVE(
JSON_REMOVE(
document,
JSON_UNQUOTE(JSON_SEARCH(document, 'one', '1-1-17'))
),
JSON_UNQUOTE(JSON_SEARCH(document, 'one', '1-1-16') )
) AS j
FROM json_meta;
+--------------------------------------+
| j |
+--------------------------------------+
| ["1-1-2", "1-1-3", "1-2-2", "1-2-3"] |
+--------------------------------------+
But this can't be done in a single query without knowing the order.
You can do it in two queries, one to get the paths and then pick which path to use at each nesting level:
mysql> select json_unquote(json_search(document, 'one', '1-1-17')),
json_unquote(json_search(document, 'one', '1-1-16'))
into #path17, #path16 from json_meta;
mysql> select json_remove(json_remove(document, greatest(#path16, #path17)), least(#path16, #path17)) as j from json_meta;
+--------------------------------------+
| j |
+--------------------------------------+
| ["1-1-2", "1-1-3", "1-2-2", "1-2-3"] |
+--------------------------------------+
If you had three or more things to remove, you'd have to sort the paths yourself and build the query in the right order.
Another solution is to fetch the whole document into an application where you have access to a JSON library to explode the elements into an array. Then you can eliminate array elements, re-marshal the array back into JSON and update the database.
If you upgrade to MySQL 8.0, you could use JSON_TABLE() to explode the array, filter out the elements you don't want, then implode them back into an array with JSON_ARRAYAGG().
This is all sounding more and more complex. JSON generally makes SQL queries harder, not easier, if you want to access individual elements of an array or fields of an object using SQL.
Demo:
mysql> select j.* from json_meta cross join json_table(document, '$[*]' columns (value varchar(10) path '$')) as j;
+--------+
| value |
+--------+
| 1-1-2 |
| 1-1-3 |
| 1-2-2 |
| 1-2-3 |
| 1-1-16 |
| 1-1-17 |
+--------+
mysql> select j.* from json_meta cross join json_table(document, '$[*]' columns (value varchar(10) path '$')) as j where value not in ('1-1-16', '1-1-17');
+-------+
| value |
+-------+
| 1-1-2 |
| 1-1-3 |
| 1-2-2 |
| 1-2-3 |
+-------+
mysql> select json_arrayagg(value) as document from json_meta cross join json_table(document, '$[*]' columns (value varchar(10) path '$')) as j where value not in ('1-1-16', '1-1-17');
+--------------------------------------+
| document |
+--------------------------------------+
| ["1-1-2", "1-1-3", "1-2-2", "1-2-3"] |
+--------------------------------------+
The best choice for simplicity and efficiency, as well as ease of code development, is to store multi-valued attributes in a normalized manner. Then you can write the SQL query this way:
DELETE FROM MyAttribute WHERE entity_id = 10 AND value IN ('1-1-16', '1-1-17');

Get row from mysql using specific value with regexp ( json string )

I'm storing permissions into DB with Array JSON String, and i want select them by permission specific permission. at this time I'm selecting them like this:
1 | Dog | [3,4]
2 | Cat | [33,4]
3 | Tiger | [5,33,4]
4 | wolf | [3,5]
SELECT * FROM `pages` WHERE access REGEXP '([^"])3([^"])'
it works but not as it should work. This query gives me all records which contains 3 but also it gives which contains 33. my question is how i must format my regexp to get row by specific value into json string.
p.s i have mysql 5.5 so as i know on this version json functions is not supported
If you only have numbers in the fields, you can alter your regexp to only take values where the string you are looking for (here the '3') does not have another number immediately close to it :
SELECT * FROM `pages` WHERE access REGEXP '([^"0-9])3([^"0-9])'
REGEXP '[[:<:]]3[[:>:]]'
That is, use the "word boundary" thingies.

Fetching nested JSON data in HBase using Apache Drill

I am using Apache Drill to run SQL queries on a HBase table. The value in one of the columns is:
0: jdbc:drill:schema:hbase:zk=localhost> select cast(address['street'] as varchar(20)) from hbase.students;
+------------+
| EXPR$0 |
+------------+
| {"id": 123} |
+------------+
1 row selected (0.507 seconds)
I would like to access the id field using a query. Something like:
0: jdbc:drill:schema:hbase:zk=localhost> select tbl.address['street']['id'] from hbase.students as tbl;
+------------+
| EXPR$0 |
+------------+
| null |
+------------+
As you can see, this does not work. I am run to similar queries on JSON data in a file. My question is can I query JSON data in HBase.
OK. I found the answer to this question, in case someone else has the same requirement.
The first step is to convert the HBase data to JSON using the built-in convert_from() function. A view can be created against which the queries can be run.
> create or replace view Street as select convert_from(Students.address.street, 'JSON') json from hbase.Customer;
Then, run query against the view
> select * from Street;
> select Street.json.id from Street;
You can also use a subquery to convert the data in your HBase column into JSON:
select t.json.id
from (select convert_from(Students.address.street, 'JSON') json
from hbase.Customer) t;

MySQL search comma separated value syntax

I am using MySQL. In one of my table attributes, I have a serial number description like "SM,ST,SK" for one device.
When users enter SM or ST or SK, I want my query to return a result
My current query looks like that:
SELECT CONCAT(lvl1_id,',',lvl2_id)
FROM hier_menus
LEFT JOIN labels ON (hier_menus.id=label_id AND tbl=65 AND fld=2 AND lang_id=5)
WHERE
hm_type=13 AND lvl1_id=141 AND lvl2_id=id AND label='".addslashes($serial)."'";
It is only able to look at the first comma part of serial number column. When users enter ST, it will not return anything.
Is it possible to search the whole of the long string "SM,ST,SK" to return a matching row?
mysql> select find_in_set('SK', 'SM,ST,SK');
+-------------------------------+
| find_in_set('SK', 'SM,ST,SK') |
+-------------------------------+
| 3 |
+-------------------------------+
1 row in set (0.00 sec)
mysql> select find_in_set('SP', 'SM,ST,SK');
+-------------------------------+
| find_in_set('SP', 'SM,ST,SK') |
+-------------------------------+
| 0 |
+-------------------------------+
You are looking for find_in_set,
however, this is not an optimize solution
you should seek to normalize your serial number into another table,
where each SM,ST, and SK is stored as one row
another way is to convert the data type to set
Try FIND_IN_SET():
SELECT ... WHERE FIND_IN_SET($serial, label)
and as ajreal's pointed out, don't use addslashes. use mysql_real_escape_string (or whatever your DB abstraction library provides). addslashes is hopelessly broken and WILL allow someone to attack your database with ease.