Update nested Json in Mysql/Mariadb - json

{
"people": {
"Man": {
"Employee": "50",
"Student": "91",
"Artist": "80",
"Clark": "50"
},
"Woman": {
"Employee": "21",
"Student": "01",
"Artist": "00",
"k3": "30",
"Clark": "68"
}
}
}
How can I Update Employee Value From Man Object. I want something similar to below mysql query.
UPDATE TABLE
SET Column = JSON_SET(Column, '$.people.Man.Employee', '51')
WHERE Id=1234567890;
MariaDB [**********]> SELECT VERSION();
+--------------------------------------------+
| VERSION() |
+--------------------------------------------+
| 10.3.17-MariaDB-1:10.3.17+maria~bionic-log |
+--------------------------------------------+

Firstly, fix the keys within the JSON column by adding quotes for each, and values by adding quotes for some which starts with zeroes and go on with other digits.
Then apply :
UPDATE tab
SET Col = JSON_SET(
Col,
"$.people.Man.Employee", "50",
"$.people.Man.Employee", "51"
)
WHERE ID = 1234567890;
Demo

Even, I don't see any problem with the following
UPDATE tab
SET
Col = JSON_SET(Col, '$.people.Man.Employee', '51')
WHERE
Id = 1234567890;
Demo

Related

How to update entire JSON object in JSONB postgres column except 1 field

for example I have a table:
CREATE TABLE fruit(id bigint, data jsonb);
and a row for example is:
1,
{
"type": "pinapple",
"store1": {
"first_added": "<some timestamp>",
"price": "10",
"store_id": "1",
"comments": "some comments..."
},
"store2": {
"first_added": "<some timestamp>",
"price": "11",
"store_id": "2",
"comments": "some comments..."
},
.... more stores
}
In case of update I have the fruit id and store data :
1,
"store1": {
"price": "12",
"store_id": "1",
"comments": "some comments...V2"
}
I want to update entire store object in fruit entry (for store1), except the first_added field.
Any idea how I can accomplish it via JSONB operators or functions?
Thanks
You can use
UPDATE fruit
SET data = data || jsonb_set($1::jsonb, '{store1,first_added}', data#>'{store1,first_added}')
WHERE id = 1;
(online demo)
where the parameter $1 is set to the value {"store1": {"price": "12", "store_id": "1", "comments": "some comments...V2"}}.
Or if you need the key to be dynamic, use
UPDATE fruit
SET data = jsonb_set(data, ARRAY[$2::text], jsonb_set($1::jsonb, '{first_added}', data->$2->'first_added'))
WHERE id = 1;
(online demo)
You can use the jsonb_set function to change the desired element, then use the jsonb_build_object function to create a new dataset, then concatenate the data with the || operator to keep the rest of the data(first_added,...)
update table1
set data = jsonb_set(data, '{store1}', jsonb_build_object('first_added', data->'store1'->'first_added', 'price', 12, 'store_id', 1, 'comments', 'some comments...V2'))
where id = 1;
Demo in DBfiddle

Postgres: count rows in jsonb for a specific key

I have a table with two labels: id INT and value JSONB. In value I have a json object props with keys id_1, id_2, and so on, with their respective values.
Is there a way to count the rows where the JSON object props has a specific key, such as id_1?
In this example, there should be two results: rows 1 and 4.
id | value
1 | {"name": "Jhon", "props": {"id_1": {"role": "role1", "class": "class1"}, "id_2": {"role": "role2", "class": "class2"}}}
2 | {"name": "Frank", "role": ["role1", "role2"]}
3 | {"name": "Bob", "props": {"id_3": {"role": "role3", "class": "class3"}, "id_4": {"role": "role4"}}}
4 | {"name": "Carl", "props": {"id_5": {"role": "role5", "class": "class5"}, "id_1": {"class": "class6"}}}
I tried something like this, but to make it work, I have to also specify the value, but the value could change for every row. For example, with this query, I only get one row back.
SELECT count(value)
FROM "myTable"
where value->'props' ->> 'id_1' = '{"role": "role1", "class": "class1"}'
Try this-
SELECT COUNT(z.*) FROM (
SELECT id, value->'props'->>'id_1' as val FROM "myTable" ) z WHERE z.val
IS NOT NULL
Use the ? operator to test whether a key exists, regardless of value.
SELECT count(*)
FROM "myTable"
where value -> 'props' ? 'id_1

Update a nested value from a Json field

Consider this table:
DROP TABLE IF EXISTS `example`;
CREATE TABLE `example` (
`id` int NOT NULL AUTO_INCREMENT,
`content` json NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
And these rows:
INSERT INTO example(content)
VALUES (
'[ { "date": "1617210148", "name": "John", "status": "0" },
{ "date": "1617210148", "name": "Jack", "status": "0" },
{ "date": "1617210148", "name": "Henry", "status": "0" }]'
);
I'd like to update the value of the key status where name = Jack to 1
The result would be:
{ "date": "1617210148", "name": "Jack", "status": "1" }
How can I do this using JSON_REPLACE() or JSON_SET() in a SQL query (I'm aiming for a partial update since I'm using MySQL 8.0.25)?
This is very awkward, nearly impossible with MySQL's JSON functions.
You can use JSON_REPLACE() or JSON_SET(), but both require that you know the path to the field you want to change. So in this case, we can see that the array element is $[1] but if you didn't know that, you couldn't use this solution.
mysql> select json_pretty(json_replace(content, '$[1].status', '1')) as j
from example\G
*************************** 1. row ***************************
j: [
{
"date": "1617210148",
"name": "John",
"status": "0"
},
{
"date": "1617210148",
"name": "Jack",
"status": "1"
},
{
"date": "1617210148",
"name": "Henry",
"status": "0"
}
]
The question like yours has come up before on Stack Overflow, for example JSON update single value in MySQL table. The solution in that case depends on you knowing which array element your pseudo-record exists in.
You can get the path to a JSON element with JSON_SEARCH(), but you can only search by value, not by a key/value pair. If "Jack" occurs in some other field, that would also be found.
mysql> select json_unquote(json_search(content, 'one', 'Jack')) as path from example;
+-----------+
| path |
+-----------+
| $[1].name |
+-----------+
To search for a key/value pair, you need to use JSON_TABLE() and that requires you upgrade to MySQL 8.0. And that doesn't tell you the path to the element, it only allows you to return a specific row out of the array.
mysql> select j.* from example cross join json_table(content, '$[*]' columns(
date int unsigned path '$.date',
name varchar(10) path '$.name',
status int path '$.status')
) as j where name = 'Jack';
+------------+------+--------+
| date | name | status |
+------------+------+--------+
| 1617210148 | Jack | 0 |
+------------+------+--------+
Here's a trick: You can extract the name field, and that turns into an array of those values:
mysql> select json_extract(content, '$[*].name') as a from example;
+---------------------------+
| a |
+---------------------------+
| ["John", "Jack", "Henry"] |
+---------------------------+
Then you can search that array to get the array position:
mysql> select json_search(json_extract(content, '$[*].name'), 'one', 'Jack') as root from example;
+--------+
| root |
+--------+
| "$[1]" |
+--------+
Some unquoting and adding .status and you can get the full path to the field you want to update:
mysql> select concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status') as path from example;
+-------------+
| path |
+-------------+
| $[1].status |
+-------------+
Now use it in a JSON_SET() call:
mysql> select json_pretty(
json_set(content,
concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status'),
'1')) as newcontent
from example\G
*************************** 1. row ***************************
newcontent: [
{
"date": "1617210148",
"name": "John",
"status": "0"
},
{
"date": "1617210148",
"name": "Jack",
"status": "1"
},
{
"date": "1617210148",
"name": "Henry",
"status": "0"
}
]
Using this in an UPDATE looks like this:
mysql> update example set content = json_set(content, concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status'), '1');
That's a long way to go. Now compare how difficult that is to:
UPDATE content SET status = 1 WHERE name = 'Jack';
Storing data in a JSON document when you eventually want to use SQL expressions to search or update individual fields within the JSON document is a costly mistake. It increases the complexity of any code you write to do it, and the developer who needs to take over maintenance of your code after you have moved on to another project will curse your name.

Convert MySQL query result to JSON

The project I'm working on requires to save all of the DB operations. So when there will be added new user I've to log the date, operation type like 'INSERT', 'UPDATE', 'DELETE' and all user data. The project is in the development phase so the columns in User table are changing.
This what I plan to do is to select the new user data from the Users table and insert them to UserLog table as a JSON column.
Is it possible to convert SELECT * FROM table_name to JSON format?
I know that there is a possibility to convert separated columns by JSON_OBJECT function, but as I mentioned above, the columns are floating so I would be forced to change the JSON_OBJECT names each time I change anything in the main table. And there are a lot of tables!
It should work like this:
CREATE TABLE Users (
id INT(1) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstName VARCHAR(30) NOT NULL,
lastName VARCHAR(30) NOT NULL,
email VARCHAR(50),
)
The query:
SELECT * FROM Users;
Should return:
[
{
"id": 1,
"firstName": "Lucas",
"lastName": "Smith",
"email": "lucas#def.com"
},
{
"id": 2,
"firstName": "Ben",
"lastName": "Kovalsky",
"email": "ben#def.com"
},
...
]
Is there a simple solution to solve this problem? If not, what is your strategy for logging DB operations?
I'm not up to date with MySQL as I switched over to PostgreSQL but I found that the recent MySQL, from version 8, supports JSON:
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id', `id`,
'firstName', `firstName`,
'lastName', `lastName`,
'email', `email`
)
)
FROM Users;
should work.
Edit, sources:
https://dev.mysql.com/doc/refman/8.0/en/json.html#json-values
https://dev.mysql.com/doc/refman/8.0/en/group-by-functions.html#function_json-arrayagg
I know this is an old thread, but for anyone still facing this issue, there is a way to convert the result set into json without knowing the column names. The key is to get the names of the columns in a string like 'column_1', column_1, 'column_2', column_2, ... and then use this string in a prepared query.
SET #column_name_string_for_query = "";
SHOW COLUMNS
FROM your_table_name
WHERE #column_name_string_for_query := TRIM(", " FROM CONCAT("'", Field, "', ", Field, ", ", #column_name_string_for_query));
SET #query_string = concat("
SELECT JSON_ARRAYAGG(JSON_OBJECT(", #column_name_string_for_query, "))
FROM your_table_name"
);
PREPARE statement FROM #query_string;
EXECUTE statement;
DEALLOCATE PREPARE statement;
You could also get the column names from INFORMATION_SCHEMA.COLUMNS, but that only works for tables that are not temporary tables. The solution above works for both temporary tables and normal tables.
You could also save this as a stored procedure for ease of use.
Normally converting the output to JSON or any other format is a job for the programming language or your mySQL IDE, but there is a way also from mySQL.
https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-shell-json-output.html
https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-shell-json-wrapping.html
Directly from documentation:
MySQL localhost:33060+ ssl world_x JS > shell.options.set('resultFormat','json')
MySQL localhost:33060+ ssl world_x JS > session.sql("select * from city where countrycode='AUT'")
{
"ID": 1523,
"Name": "Wien",
"CountryCode": "AUT",
"District": "Wien",
"Info": {
"Population": 1608144
}
}
{
"ID": 1524,
"Name": "Graz",
"CountryCode": "AUT",
"District": "Steiermark",
"Info": {
"Population": 240967
}
}
{
"ID": 1525,
"Name": "Linz",
"CountryCode": "AUT",
"District": "North Austria",
"Info": {
"Population": 188022
}
}
{
"ID": 1526,
"Name": "Salzburg",
"CountryCode": "AUT",
"District": "Salzburg",
"Info": {
"Population": 144247
}
}
{
"ID": 1527,
"Name": "Innsbruck",
"CountryCode": "AUT",
"District": "Tiroli",
"Info": {
"Population": 111752
}
}
{
"ID": 1528,
"Name": "Klagenfurt",
"CountryCode": "AUT",
"District": "Kärnten",
"Info": {
"Population": 91141
}
}
6 rows in set (0.0031 sec)
Also, adding the JSON_OBJECT, that is available from 5.7+, see the answer here.
mysql> SELECT JSON_OBJECT('id', 87, 'name', 'carrot');
+-----------------------------------------+
| JSON_OBJECT('id', 87, 'name', 'carrot') |
+-----------------------------------------+
| {"id": 87, "name": "carrot"} |
+-----------------------------------------

How can I use MariaDB to pull values from multiple objects from a JSON array?

In MariaDB 10.2.19, I have a table named forms with a column template which always contains a JSON array of objects. Some of these objects will have properties I want to return: name (should always be present), rule, and parameters. How can I return just these three properties from the entire array, but only for objects on which rule is present?
A sample array (formatted for easier viewing):
[{
"label": "Employed?",
"class": "select",
"name": "employed",
"parameters": "Yes",
"rule": "in"
},
{
"label": "Breed of dog?",
"class": "select",
"name": "breed",
"parameters": "spaniel, collie, mix",
"rule": "in"
},
{
"label": "Number",
"class": "text",
"name": "breed"
}]
If you are using MySQL 8.0.4 or later one way is using JSON_TABLE:
mysql> SELECT * FROM foo;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| data |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [{"name": "employed", "rule": "in", "class": "select", "label": "Employed?", "parameters": "Yes"}, {"name": "breed", "rule": "in", "class": "select", "label": "Breed of dog?", "parameters": "spaniel, collie, mix"}, {"name": "breed", "class": "text", "label": "Number"}] |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0,00 sec)
mysql> SELECT name, parameters
FROM foo,
JSON_TABLE (
foo.data,
"$[*]" COLUMNS (
name VARCHAR(100) PATH "$.name",
rule VARCHAR(100) PATH "$.rule",
parameters VARCHAR(100) PATH "$.parameters")
) AS t
WHERE rule IS NOT NULL;
+----------+----------------------+
| name | parameters |
+----------+----------------------+
| employed | Yes |
| breed | spaniel, collie, mix |
+----------+----------------------+
2 rows in set (0,00 sec)
https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html
You can accomplish this by using a dedicated numbers_table table:
SELECT
JSON_VALUE(f.template,CONCAT('$[',n.number,'].name')) AS `name`,
JSON_VALUE(f.template,CONCAT('$[',n.number,'].rule')) AS `rule`,
JSON_VALUE(f.template,CONCAT('$[',n.number,'].parameters')) AS `parameters`
FROM forms AS f
JOIN numbers_table AS n
WHERE
n.number < JSON_LENGTH(f.template) AND
JSON_VALUE(f.template,CONCAT('$[',n.number,'].rule')) IS NOT NULL;
The numbers_table table contains a single column called number which starts at 0 and can be as long as your use cases require (I have 1000 values 0 to 999). It is very useful for extracting each element of a JSON_ARRAY field into its own row.
The first WHERE condition makes sure we only use as many numbers as there are elements in the JSON_ARRAY (template in your case).
The second WHERE condition eliminates the ones that don't have a rule as per your use case.