How to insert new value in the middle of a JSON array? - mysql

I have a JSON type field in my table, which has a value like this
[1, 3]
and I want to insert another value in the middle of the array, using something like a json splice function (I know it doesn't exist)
/* JSON_ARRAY_SPLICE(array, start, deleteCount, itemToInsert) */
JSON_ARRAY_SPLICE('[1, 3]', 1, 0, 2)
>>> [1, 2, 3]
I'm using php and I can make a function in order to do it, but i'm looking for a mysql solution. Is there a way I can achieve this?

mysql> set #j = '[1, 3]';
mysql> select json_array_insert(#j, '$[1]', 2) as new_j;
+-----------+
| new_j |
+-----------+
| [1, 2, 3] |
+-----------+

Related

MySQL merge json field with new data while removing duplicates, where the json values are simple scalar values

Suppose that I have a MySQL table with a JSON field that contains only numbers, like this (note: using MySQL 8):
CREATE TABLE my_table (
id int,
some_field json
);
Sample data:
id: 1
some_field: [1, 2, 3, 4, 5]
id: 2
some_field: [3, 6, 7]
id: 3
some_field: null
I would like to merge another array of data with the existing values of some_field, while removing duplicates. I was hoping that this might work, but it didn't:
update my_table set some_field = JSON_MERGE([1, 2, 3], some_field)
The result of this would be:
id: 1
some_field: [1, 2, 3, 4, 5]
id: 2
some_field: [1, 2, 3, 6, 7]
id: 3
some_field: [1, 2, 3]
Considering you have 3 records in your table and you want to merge 1 and 2 as mentioned in your example.
I hope JavaScript is suitable to follow through for you.
// Get both the records
const records = db.execute(“SELECT id, some_field FROM my_table WHERE id=1 OR id=2”);
// You get both the rows.
// Merging row1, you can either use the Set data structure if you’re dealing with numbers like your example, or you could loop using a map and use the spread operator if using JSON. Since your object is an array, I’ll just be explaining to merge 2 arrays.
records[0].some_field = Array.from(new Set(records[0].some_field + record[1].some_field))
// Same for second record.
records[1].some_field = Array.from(new Set(records[0].some_field + record[1].some_field))
// Now update both the records in the database one by one.

Select if array contains an element of another array

I have a table which has a JSON type field where I save a number array like [1, 2, 3, 4].
I want to select records in which its array set contains at least one element of another array I have in a php script.
I know that the JSON_CONTAINS function can be used to see if my array contains an element, but how can I select if both arrays has at least a common number (no matter in what index).
For example:
[1, 2, 3] and [5, 0, 2] -> True
[9, 2, 1] and [0, 5, 3] -> False
[4, 0, 2] and [4, 2, 6] -> True
Currently, Im using multiple JSON_CONTAINS to check if there are common elements, this way:
SELECT *
FROM mytable
WHERE JSON_CONTAINS(ar, 0, '$') OR
JSON_CONTAINS(ar, 1, '$') OR
JSON_CONTAINS(ar, 2, '$')
But I guess there may be a more elegant way of doing this.
I searched but couldn't find the appropiate function, but if this is a dupe, let me know.
Thanks in advance!
https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-overlaps
mysql> SELECT JSON_OVERLAPS("[1,3,5,7]", "[2,5,7]");
+---------------------------------------+
| JSON_OVERLAPS("[1,3,5,7]", "[2,5,7]") |
+---------------------------------------+
| 1 |
+---------------------------------------+

Is there an opposite of MySQL's JSON_ARRAY_APPEND?

I used UPDATE table SET col = JSON_ARRAY_APPEND(col, '$', 'BAZ') to add a value to my json column:
Before: ["FOO", "BAR"]
After: ["FOO", "BAR", "BAZ"]
How can I now remove the value, i.e. perform the reverse of JSON_ARRAY_APPEND? I've tried the following but it doesn't seem to pick up the value.
UPDATE table SET col = JSON_REMOVE(col, '$.BAZ')
You can remove array elements by position, not by value.
select json_remove('["FOO", "BAR", "BAZ"]', '$[2]') as array;
+----------------+
| array |
+----------------+
| ["FOO", "BAR"] |
+----------------+
But you can find the position with JSON_SEARCH():
select json_search('["FOO", "BAR", "BAZ"]', 'one', 'BAZ') as path;
+--------+
| path |
+--------+
| "$[2]" |
+--------+
You can see it strangely puts JSON double-quotes around that path. So you have to unquote it:
select json_unquote(json_search('["FOO", "BAR", "BAZ"]', 'one', 'BAZ')) as path;
+------+
| path |
+------+
| $[2] |
+------+
Then put it all together:
select json_remove('["FOO", "BAR", "BAZ"]', json_unquote(json_search('["FOO", "BAR", "BAZ"]', 'one', 'BAZ'))) as array;
+----------------+
| array |
+----------------+
| ["FOO", "BAR"] |
+----------------+
This would be a lot easier if you didn't use JSON arrays. Instead of using an array, put multi-valued attributes in a child table, with one value per row. Then you can delete using traditional SQL:
DELETE FROM child_table WHERE somvalue = 'BAZ';
I have answered a bunch of questions about using JSON in MySQL here on Stack Overflow, and I have yet to see an instance where using JSON is easier than using normalized tables.

Preventing duplicates in JSON array in MySQL

I have a JSON-typed column in a MySQl table. This column stores an array of integer values, like '[1,2,3]'.
What I want is: when I insert a new value into this array using JSON_ARRAY_INSERT/JSON_ARRAY_APPEND, if the value already exists, simply ignore it.
How can I do this within MySQL? That is, NOT checking duplicates from calling scripts like PHP or Node.js, etc.
Update: I'd prefer to use a built-in MySQL mechanism for this purpose, because if done in external scripts, there would be a small chance that duplicates happen due to race conditions, even if I check against duplicates before storing the data.
Thanks!
Without knowing your exact table structure it's difficult to give a precise answer, but the following queries demonstrate how it's done with variables. You would just need to adapt this according to your columns.
set #j='["1","2","3"]';
set #v = "4";
select ifnull(json_search(#j, 'one', #v), #j:=json_array_append(#j, '$', #v));
set #v = "1";
select ifnull(json_search(#j, 'one', #v), #j:=json_array_append(#j, '$', #v));
select #j;
Output:
["1", "2", "3", "4"]
Hope this helps.
Schema (MySQL v5.7)
CREATE TABLE test (
id INT,
js JSON
);
INSERT INTO test (id, js) VALUES (1, '[1,2,3]');
INSERT INTO test (id, js) VALUES (2, '[1,2]');
Query #1
select * from test;
| id | js |
| --- | --------- |
| 1 | [1, 2, 3] |
| 2 | [1, 2] |
Query #2
UPDATE test
SET js = JSON_ARRAY_APPEND (js, '$', 3)
WHERE id = 1 and not JSON_CONTAINS(js, '3', '$');
UPDATE test
SET js = JSON_ARRAY_APPEND (js, '$', 3)
WHERE id = 2 and not JSON_CONTAINS(js, '3', '$');
Query #3
select * from test;
| id | js |
| --- | --------- |
| 1 | [1, 2, 3] |
| 2 | [1, 2, 3] |
set #j='[1, 2, 3]';
set #v=1;
SELECT IF(JSON_CONTAINS(#j, CAST(#v AS JSON), '$') = 1, #j, json_array_append(#j, '$', CAST(#v AS JSON)));
OUTPUT:
[1, 2, 3]
set #j='[1, 2, 3]';
set #v=4;
SELECT IF(JSON_CONTAINS(#j, CAST(#v AS JSON), '$') = 1, #j, json_array_append(#j, '$', CAST(#v AS JSON)));
OUTPUT:
[1, 2, 3, 4]
DECLARE lc,leng,du INT(50);
DECLARE conc,vb VARCHAR(50);
DECLARE new_array JSON;
SET new_array='[]';
SET lc=0;
SET leng=JSON_LENGTH(first_array);
WHILE lc<leng DO
SET conc=(SELECT CONCAT('$[',lc,']'));
IF JSON_CONTAINS(JSON_UNQUOTE(new_array),CONCAT('"',JSON_UNQUOTE(JSON_EXTRACT(first_array,conc)),'"')) THEN
SET du=1;
ELSE
SET new_array=(SELECT JSON_ARRAY_APPEND(new_array,'$',(JSON_UNQUOTE(JSON_EXTRACT(first_array,conc)))));
end if;
SET lc=lc+1;
END WHILE;
RETURN new_array;
This function returns non duplicate JSON array in MySQL
first array is a json_array input parrameter

How to search JSON array in MySQL?

Let's say I have a JSON column named data in some MySQL table, and this column is a single array. So, for example, data may contain:
[1,2,3,4,5]
Now I want to select all rows which have a data column where one of its array elements is greater than 2. Is this possible?
I tried the following, but seems it is always true regardless of the values in the array:
SELECT * from my_table
WHERE JSON_EXTRACT(data, '$[*]') > 2;
You may search an array of integers as follows:
JSON_CONTAINS('[1,2,3,4,5]','7','$') Returns: 0
JSON_CONTAINS('[1,2,3,4,5]','1','$') Returns: 1
You may search an array of strings as follows:
JSON_CONTAINS('["a","2","c","4","x"]','"x"','$') Returns: 1
JSON_CONTAINS('["1","2","3","4","5"]','"7"','$') Returns: 0
Note: JSON_CONTAINS returns either 1 or 0
In your case you may search using a query like so:
SELECT * from my_table
WHERE JSON_CONTAINS(data, '2', '$');
SELECT JSON_SEARCH('["1","2","3","4","5"]', 'one', "2") is not null
is true
SELECT JSON_SEARCH('["1","2","3","4","5"]', 'one', "6") is not null
is false
Since MySQL 8 there is a new function called JSON_TABLE.
CREATE TABLE my_table (id INT, data JSON);
INSERT INTO my_table VALUES
(1, "[1,2,3,4,5]"),
(2, "[0,1,2]"),
(3, "[3,4,-10]"),
(4, "[-1,-2,0]");
SELECT DISTINCT my_table.*
FROM my_table, JSON_TABLE(data, "$[*]" COLUMNS(nr INT PATH '$')) as ids
WHERE ids.nr > 2;
+------+-----------------+
| id | data |
+------+-----------------+
| 1 | [1, 2, 3, 4, 5] |
| 3 | [3, 4, -10] |
+------+-----------------+
2 rows in set (0.00 sec)
I use a combination of JSON_EXTRACT and JSON_CONTAINS (MariaDB):
SELECT * FROM table WHERE JSON_CONTAINS(JSON_EXTRACT(json_field, '$[*].id'), 11, '$');
I don't know if we found the solution.
I found with MariaDB a way, to search path in a array. For example, in array [{"id":1}, {"id":2}], I want find path with id equal to 2.
SELECT JSON_SEARCH('name_field', 'one', 2, null, '$[*].id')
FROM name_table
The result is:
"$[1].id"
The asterisk indicate searching the entire array
This example works for me with mysql 5.7 above
SET #j = '{"a": [ "8428341ffffffff", "8428343ffffffff", "8428345ffffffff", "8428347ffffffff","8428349ffffffff", "842834bffffffff", "842834dffffffff"], "b": 2, "c": {"d": 4}}';
select JSON_CONTAINS(JSON_EXTRACT(#j , '$.a'),'"8428341ffffffff"','$') => returns 1
notice about " around search keyword, '"8428341ffffffff"'
A possible way is to deal with the problem as string matching. Convert the JSON to string and match.
Or you can use JSON_CONTAINS.
You can use JSON extract to search and select data
SELECT data, data->"$.id" as selectdata
FROM table
WHERE JSON_EXTRACT(data, "$.id") = '123'
#ORDER BY c->"$.name";
limit 10 ;
SET #doc = '[{"SongLabels": [{"SongLabelId": "111", "SongLabelName": "Funk"}, {"SongLabelId": "222", "SongLabelName": "RnB"}], "SongLabelCategoryId": "test11", "SongLabelCategoryName": "曲风"}]';
SELECT *, JSON_SEARCH(#doc, 'one', '%un%', null, '$[*].SongLabels[*].SongLabelName')FROM t_music_song_label_relation;
result: "$[0].SongLabels[0].SongLabelName"
SELECT song_label_content->'$[*].SongLabels[*].SongLabelName' FROM t_music_song_label_relation;
result: ["Funk", "RnB"]
I have similar problem, search via function
create function searchGT(threshold int, d JSON)
returns int
begin
set #i = 0;
while #i < json_length(d) do
if json_extract(d, CONCAT('$[', #i, ']')) > threshold then
return json_extract(d, CONCAT('$[', #i, ']'));
end if;
set #i = #i + 1;
end while;
return null;
end;
select searchGT(3, CAST('[1,10,20]' AS JSON));
This seems to be possible with to JSON_TABLE function. It's available in mysql version 8.0 or mariadb version 10.6.
With this test setup
CREATE TEMPORARY TABLE mytable
WITH data(a,json) AS (VALUES ('a','[1]'),
('b','[1,2]'),
('c','[1,2,3]'),
('d','[1,2,3,4]'))
SELECT * from data;
we get the following table
+---+-----------+
| a | json |
+---+-----------+
| a | [1] |
| b | [1,2] |
| c | [1,2,3] |
| d | [1,2,3,4] |
+---+-----------+
It's possible to select every row from mytable wich has a value greater than 2 in the json array with this query.
SELECT * FROM mytable
WHERE TRUE IN (SELECT val > 2
FROM JSON_TABLE(json,'$[*]'
columns (val INT(1) path '$')
) as json
)
Returns:
+---+-----------+
| a | json |
+---+-----------+
| c | [1,2,3] |
| d | [1,2,3,4] |
+---+-----------+