Mysql - How can I merge two json arrays of strings without duplicates? - mysql

If I have two json arrays of strings in mysql, is there a native(or not native) way to merge these two arrays into one with unique strings?
If I try json_merge I get the following result with duplicates:
set #array1 =JSON_EXTRACT('["apple","pear","banana"]', '$');
set #array2 =JSON_EXTRACT('["pear","banana","apple","kiwi"]', '$');
select json_merge(#array1,#array2);
> ["apple", "pear", "banana", "pear", "banana", "apple", "kiwi"]
And If is try json_merge_preserve gives me the same result:
set #array1 =JSON_EXTRACT('["apple","pear","banana"]', '$');
set #array2 =JSON_EXTRACT('["pear","banana","apple","kiwi"]', '$');
select json_merge_preserve(#array1,#array2);
> ["apple", "pear", "banana", "pear", "banana", "apple", "kiwi"]
Is there a function that will return the unique array?
["apple", "banana", "pear", "kiwi"]
Edit: json_merge_patch doesn't work because it only replaces the first array with the second:
set #array1 =JSON_EXTRACT('["apple","grape","banana"]', '$');
set #array2 =JSON_EXTRACT('["pear","banana","apple","kiwi"]', '$');
select json_merge_patch(#array1,#array2);
> ["pear", "banana", "apple", "kiwi"]
In this case I lose "grape". I believe that the logic in patch is 0 : 'val', 1:'val2' merge with 0:val3 then 0 : 'val3', 1:'val2'

If the question still lives, here's a simple solution using MySQL 8.0's JSON_TABLE.
set #a1 ='["apple","grape","banana","banana","pear"]';
set #a2 ='["pear","banana","apple","kiwi","banana","apple"]';
select fruit
from json_table(
json_merge_preserve(#a1, #a2),
'$[*]' columns (
fruit varchar(255) path '$'
)
) as fruits
group by fruit; # get distinct values
# gives
apple
grape
banana
pear
kiwi
To get a one-line response, we have to drop group by and get a bit more creative.
Unfortunately, JSON_ARRAYAGG doesn't support distinct directive, so we'll have to use GROUP_CONCAT:
select group_concat(distinct fruit)
from json_table(
json_merge_preserve(#a1, #a2),
'$[*]' columns (
fruit varchar(255) path '$'
)
) as fruits;
# without group by directive!
# gives: apple,banana,grape,kiwi,peas
To get a proper json array on-line response, we just play around with CONCATs:
select cast(
concat('["', group_concat(distinct fruit separator '", "'), '"]')
as json)
...
# gives: ["apple", "banana", "grape", "kiwi", "pear"]
EDIT:
I've found out a proper JSON_ARRAYAGG solution using one more nested virtual table to group results in.
select json_arrayagg(fruit)
from (
select fruit
from json_table(
json_merge_preserve(#a1, #a2),
'$[*]' columns (
fruit varchar(255) path '$'
)
) as fruits
group by fruit -- group here!
) as unique_fruits;
Read my Best Practices for using MySQL as JSON storage :)

After too much thinking, and thanks to #lefred. I found a hack that can accomplish this.
This is way too hacky, but i will publish it while someone else comes with a better implementation or the mysql guys make a proper function for this.
First, we replace the string strategically to create a json object instead of an array.
Then, we use json_merge_path and finally we use json_keys to obtain an array :V
set #array1 ='["apple","grape","banana","banana","pear"]';
set #array2 ='["pear","banana","apple","kiwi","banana","apple"]';
set #aux1 = REPLACE(REPLACE(REPLACE(#array1, ',', ' : "1", '), ']', ' : "1" }'), '[', '{');
set #aux2 = REPLACE(REPLACE(REPLACE(#array2, ',', ' : "1", '), ']', ' : "1" }'), '[', '{');
select #aux1, #aux2;
select json_keys(json_merge_patch(json_extract(#aux1, '$'),json_extract(#aux2,'$')))
> ["kiwi", "pear", "apple", "grape", "banana"]

Use SELECT DISTINCT:
set #array1 =JSON_EXTRACT('["apple","grape","banana"]', '$');
set #array2 =JSON_EXTRACT('["pear","banana","apple","kiwi"]', '$');
select json_arrayagg(fruit) from (
select
distinct fruit
from json_table(
json_merge_preserve(#array1, #array2),
'$[*]' columns (fruit varchar(255) path '$')
) fruits
) f;

According to the documentation, json_merge_preserve preserves duplicates. Also, if you are using MySQL 8.0.3 or over, json_merge is deprecated and json_merge_preserve should be used. I think that you need to use JSON_MERGE_PATCH.
More details here https://database.guide/json_merge_preserve-merge-multiple-json-documents-in-mysql/

Related

How to Create a JSON_OBJECT in MySql From Rows of Key-Value Data

In MySQL, I am trying to create a JSON_OBJECT from rows of data containing key-value pairs.
Here is the sample data:
CREATE TABLE TempValuePair( MyKey VARCHAR(64), MyValue VARCHAR(64) );
INSERT INTO TempValuePair VALUE
('Country', 'Argentina'),
('Capital', 'Buenos Aires'),
('Population', 45810000 );
The following statement seems to return an argument that conforms to the JSON_OBJECT requirements:
SELECT GROUP_CONCAT(
CONCAT( '\'', MyKey, '\',\'', Myvalue, '\'' )
ORDER BY MyKey
)
FROM TempValuePair;
However, the following statement fails:
SELECT GROUP_CONCAT(
CONCAT( '\'', MyKey, '\',\'', Myvalue, '\'' )
ORDER BY MyKey
)
FROM TempValuePair
);
Any advice about what I am doing wrong would be greatly appreciated. Thanks!
You seem to want json_objectagg, which is available in MySQL since version 5.7. The function aggregates key/value pairs from multiple rows into a single JSON object:
select json_objectagg(mykey, myvalue) as js from TempValuePair;
Yields:
{"Capital": "Buenos Aires", "Country": "Argentina", "Population": "45810000"}
Demo on DB Fiddle

Is there any way to join multiple table and show result of some table as objects [duplicate]

I'd like to convert result table to JSON array in MySQL using preferably only plain MySQL commands. For example with query
SELECT name, phone FROM person;
| name | phone |
| Jack | 12345 |
| John | 23455 |
the expected JSON output would be
[
{
"name": "Jack",
"phone": 12345
},
{
"name": "John",
"phone": 23455
}
]
Is there way to do that in plain MySQL?
EDIT:
There are some answers how to do this with e.g. MySQL and PHP, but I couldn't find pure MySQL solution.
New solution:
Built using Your great comments, thanks!
SELECT JSON_ARRAYAGG(JSON_OBJECT('name', name, 'phone', phone)) from Person;
Old solution:
With help from #Schwern I managed to put up this query, which seems to work!
SELECT CONCAT(
'[',
GROUP_CONCAT(JSON_OBJECT('name', name, 'phone', phone)),
']'
)
FROM person;
You can use json_object to get rows as JSON objects.
SELECT json_object('name', name, 'phone', phone)
FROM person;
This won't put them in an array, or put commas between them. You'll have to do that in the code which is fetching them.
If you're stuck on MySQL 5.6 like me, try this:
SELECT
CONCAT(
'[',
GROUP_CONCAT(
CONCAT(
'{"name":"', name, '"',
',"phone":"', phone, '"}'
)
),
']'
) as json
FROM person
There are two "group by" functions for JSON called json_arrayagg, json_objectagg.
This problem can be solved with:
SELECT json_arrayagg(
json_merge(
json_object('name', name),
json_object('phone', phone)
)
) FROM person;
This requires MySQL 5.7+.
If you need a nested JSON Array Object, you can join JSON_OBJECT with json_arrayagg as below:
{
"nome": "Moon",
"resumo": "This is a resume.",
"dt_inicial": "2018-09-01",
"v.dt_final": null,
"data": [
{
"unidade": "unit_1",
"id_unidade": 9310
},
{
"unidade": "unit_2",
"id_unidade": 11290
},
{
"unidade": "unit_3",
"id_unidade": 13544
},
{
"unidade": "unit_4",
"id_unidade": 13608
}
]
}
You can also do it like this:
CREATE DEFINER=`root`#`localhost` PROCEDURE `get_lst_caso`(
IN `codigo` int,
IN `cod_base` int)
BEGIN
DECLARE json TEXT DEFAULT '';
SELECT JSON_OBJECT(
'nome', v.nome,
'dt_inicial', v.dt_inicial,
'v.dt_final', v.dt_final,
'resumo', v.resumo,
'data', ( select json_arrayagg(json_object(
'id_unidade',`tb_unidades`.`id_unidade`,
'unidade',`tb_unidades`.`unidade`))
from tb_caso_unidade
INNER JOIN tb_unidades ON tb_caso_unidade.cod_unidade = tb_unidades.id_unidade
WHERE tb_caso_unidade.cod_caso = codigo)
) INTO json
FROM v_caso AS v
WHERE v.codigo = codigo and v.cod_base = cod_base;
SELECT json;
END
For most situations, I use DataGreap, but for big tables, it is not work.
My GIST shell script

Extract feelds as key value from a json object in mariadb

Hello I want to extract the different field values of a json object as key value pairs, but I'm not able to do that.
I tried this
SELECT JSON_EXTRACT(chapters, '$[*].Id', '$[*].Name') AS rec
FROM `Novels`
WHERE 1
but it result looks like this
["1","first Name","2","second name"]
any idea on how to convert it to something like this
{"1":"first Name","2":"second name"}
Thanks in advance!
Depending on the result, the concerned value of the chapters column should be
'[ {"Id":"1","Name":"first name"}, {"Id":"2","Name":"second name"} ]'
JSON_EXTRACT() can be applied for each element of the array in order to determine Id values as keys part, and Name values as values part.
And then, JSON_UNQUOTE() can be applied to get rid of double-quotes while generating rows for each individual array elements. JSON_OBJECTAGG is used to aggregate all those extracted objects at the last step provided that MariaDB version is 10.5+:
WITH n AS
(
SELECT #i := #i + 1 AS rn,
JSON_UNQUOTE(JSON_EXTRACT(chapters, CONCAT('$[',#i-1,'].Id'))) AS js_id,
JSON_UNQUOTE(JSON_EXTRACT(chapters, CONCAT('$[',#i-1,'].Name'))) AS js_name
FROM information_schema.tables
CROSS JOIN ( SELECT #i := 0, chapters FROM `Novels` ) n
WHERE #i < JSON_LENGTH(JSON_EXTRACT(chapters, '$[*]'))
)
SELECT JSON_OBJECTAGG(js_id,js_name) AS Result
FROM n
A Workaround might be given for DB version prior to 10.5 as
SELECT CONCAT('{',
GROUP_CONCAT(
REPLACE(
REPLACE( JSON_OBJECT(js_id,js_name) , '}', '')
, '{', '')
)
, '}') AS Result
FROM n
Demo
One option uses json_table() to unnest the array to rows (available in MySQL 8 only) then aggregation:
select
t.*,
(
select json_objectagg('id', x.id, 'name', x.name)
from json_table(
t.chapter,
'$[*]'
columns (
id int path '$.Id',
name varchar(50) path '$.Name'
)
) as x
) as obj
from mytable t

MySQL get distinct values from arbitrarily long arrays of strings across rows

I have a table with a JSON column, let's call it json_data and the column contents look like...
[{ "data": { ... }, "name": "name_1" }, { "data": { ... }, "name": "name_2" }]
[{ "data": { ... }, "name": "name_2" }]
[{ "data": { ... }, "name": "name_3" }, { "data": { ... }, "name": "name_5" }]
[{ "data": { ... }, "name": "name_4" }]
...and I am looking to get back
["name_1", "name_2", "name_3", "name_4", "name_5"]
or something of that sort. I can use JSON_EXTRACT to quite easily get each row's set of name fields...
SELECT JSON_EXTRACT(json_data, "$**.name") FROM my_table;
...so now I have rows each containing a single array of comma separated strings, and can use GROUP_CONCAT to merge them...
SELECT REPLACE(REPLACE(GROUP_CONCAT(names SEPARATOR ','), '[', ''), ']', '')
FROM (
SELECT JSON_EXTRACT(json_data, '$**.name') as names
FROM my_table
WHERE json_data <> '' -- exclude empty entries
LIMIT 10) x -- test on sample size as the table is quite large
ORDER BY NULL; -- get names from all rows
...at this point I have all the data that I want in a single row as comma separated strings...
"name_1","name_2","name_2","name_3","name_4","name_5"
except that it has duplicates (MANY of them).
It seems like it should be easy to take this and run distinct on it, but so far I cannot figure out how to split the string into all the elements and perform distinct. SUBSTRING_INDEX seems to be what I need, but that only gets individual elements... any help is appreciated!
In modern versions of MySQL (>= 8.0.4) the query would be relatively simple:
SELECT
GROUP_CONCAT(
DISTINCT JSON_QUOTE(`der`.`names`)
) `names`
FROM
`my_table`,
JSON_TABLE(`my_table`.`json_data`,
'$[*]' COLUMNS(
`names` VARCHAR(10) PATH '$.name'
)
) `der`
ORDER BY
`names`;
See db-fiddle.
However, in older versions, it's not that simple, one option may be to use a temporary table and a prepared statement:
SET #`ddl` := CONCAT('INSERT INTO `my_table` VALUES ',
(SELECT
GROUP_CONCAT(
REPLACE(
REPLACE(
REPLACE(
`json_data` -> '$**.name',
'[', '('),
']', ')'),
',', '),(')
)
FROM
`my_table`
)
);
See Rextester.
In any case, remember 5.1.7 Server System Variables::group_concat_max_len.

How to Update a Specific Object in a JSON Array in Mysql 5.7

How can I update an object in an array based on a unique value in the object?
Let's say this is my json object stored in a table called objects and in a column called content
table: objects
id: 7383
content: { data:[{id: 111, active: 1 }, {id: 222, active: 1 }, {id: 333, active: 0 }] }
I can update objects if I know the position of the element in the array with
SET content = JSON_REPLACE(content,'$.data[1].active', 0)
Where id = 7383
However, if I don't know the position of the array, but I do know the value of id (for example 222) in the object, how can I update active to 0 for the object that has id: 222 ?
Currently, it's complicated to look up numerical values with MySQL JSON functions. In a JSON like the following, it would be simple:
{"id": "222", "active": 1}
There are many ways to get what you need, I present one that can give you ideas (modify everything that is necessary):
UPDATE `objects`
SET `objects`.`content` =
JSON_REPLACE(`objects`.`content`, CONCAT('$.data',
(SELECT
JSON_UNQUOTE(
REPLACE(
JSON_SEARCH(
REPLACE(
REPLACE(
REPLACE(
`der`.`content` ->> '$.data[*].id',
', ',
'","'),
']',
'"]'),
'[',
'["'),
'one',
'222'),
'$',
'')
)
FROM (SELECT `objects`.`content`
FROM `objects`
WHERE `objects`.`id` = 7383) `der`
), '.active'), 0)
WHERE `objects`.`id` = 7383;
Beware of possible performance problems.
See dbfiddle.
In the most recent version of MySQL (>= 8.0.4), the sentence would be much simpler:
UPDATE `objects`
INNER JOIN JSON_TABLE(
`objects`.`content`,
'$.data[*]' COLUMNS(
`rowid` FOR ORDINALITY,
`id` INT PATH '$.id'
)
) `der` ON `der`.`id` = 222
SET `objects`.`content` =
JSON_REPLACE(
`objects`.`content`,
CONCAT('$.data[', `der`.`rowid` - 1, '].active'),
0)
WHERE
`objects`.`id` = 7383;
See db-fiddle.
It can be achieved by combining the functions JSON_SEARCH, which returns a dirty json path to the item you need, and then, extract the value of the jsonpath with an array index, concatenate it with subpath we want to update and use JSON_SET to set a new value to the final json path (tested with MySQL 5.7.32):
-- INPUT ------------------------------------------------
-- unique value for an object in the array
SET #unique_value = "12345";
-- object field we want to update
SET #field_to_update = '.myField';
-- new value
SET #new_value = 1;
-- PROCESSING ------------------------------------------
-- Get json path to the item with specified #unique_value
-- RESULT: $.data[6].id
SET #temp_path = ( TRIM(BOTH '"' FROM ( SELECT JSON_SEARCH(json, 'one', #unique_value, NULL, "$.data")
FROM `my-table`
WHERE `column1` = "abcd" ) ));
-- We are looking for the bracket that delimits index within the array of documents: [11]
SET #closing_bracket_index = (SELECT LOCATE(']', #temp_path));
-- Get json path with index of an object for #unique_value
-- in MySQL, string indexing starts from position 1, not a zero
-- RESULT: $.data[6]
SET #item_path = ( SELECT SUBSTRING(#temp_path, 1, #closing_bracket_index) );
-- $.data[6].myFIeld
SET #item_path_to_update = ( SELECT CONCAT(#item_path, #field_to_update) );
-- UPDATE JSON STATEMENT
UPDATE `my-table`
SET json = JSON_SET(json-column, #item_path_to_update, #new_value)
WHERE `column1` = "abcd";