How to extract json data in mysql select? - mysql

I have two tables, and I want to use a value from primary table (here: person.ref) to extract some data from a json field in a secondary table (here: person_details):
create table person (
id int(8),
ref varchar(20),
primary key (id)
);
create table person_details (
id int(8),
details json,
primary key (id)
);
SELECT JSON_EXTRACT(details, CONCAT('$.', p.ref))
FROM person p
JOIN person_details d using (id);
The person_details data is like:
{
"ref1": {
...
}
}
But that does not matter, because the sql statement itself seems to be invalid:
Result:
#3143 - Invalid JSON path expression. The error is around character position 12.
Why?

Your code should work on MySQL >= 5.7. Check this dbfiddle:
https://dbfiddle.uk/esEJ9uHd
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ref` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `person_details` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`details` json,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `person` (`id`, `ref`) VALUES (1, 'ref1');
INSERT INTO `person` (`id`, `ref`) VALUES (2, 'ref1');
INSERT INTO `person_details` (`id`, `details`) VALUES (1, '{"ref1": {"name": "Barney"}}');
INSERT INTO `person_details` (`id`, `details`) VALUES (2, '{"ref1": {"name": "Willma"}}');
SELECT JSON_EXTRACT(details, CONCAT('$.', p.ref))
FROM person p
JOIN person_details d using (id);

While the answer above is correct, it did not solve my issue, but I had to add doublequotes around p.ref, so that the concatenation results in CONCAT($."p.ref"):
SELECT JSON_EXTRACT(details, CONCAT('$."', p.ref, '"'))
...

Related

INSERT INTO SELECT FROM ON DUPLICATE KEY UPDATE: 1287 'VALUES function' is deprecated

I've created a query that does what I need but MySQL server (8.0.29) issues 1 warning. I'm wondering if it's possible to get rid of the warning.
Query:
INSERT INTO order_summary (product_id, num_orders)
SELECT product_id, COUNT(product_id) AS num_orders
FROM `order` GROUP BY product_id
ON DUPLICATE KEY UPDATE num_orders=VALUES(num_orders);
Warning:
1287 'VALUES function' is deprecated and will be removed in a future release. Please use an alias (INSERT INTO ... VALUES (...) AS alias) and replace VALUES(col) in the ON DUPLICATE KEY UPDATE clause with alias.col instead
DB structure:
CREATE TABLE `order` (
`id` int NOT NULL AUTO_INCREMENT,
`product_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `order_summary` (
`id` int NOT NULL AUTO_INCREMENT,
`product_id` int NOT NULL,
`num_orders` int NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `product_id_UNIQUE` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Thanks
INSERT INTO order_summary (product_id, num_orders)
SELECT *
FROM (
SELECT product_id, COUNT(product_id) AS num_orders
FROM `order`
GROUP BY product_id
) data
ON DUPLICATE KEY UPDATE num_orders=data.num_orders;
https://dbfiddle.uk/VZ9vhdAe
PS. If data source does not perform GROUP BY then SELECT * FROM ( .. ) data is excess, and you may refer to source tables columns immediately.

Category and subcategory recursive query SQL

I got a little problem with my recursive query.
I got a database of menu of a bar.
We got: Category, each category got sub-categories and each-subcategories got multiple items.
The database is this one and the query is linked inside:
CREATE TABLE category (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
parent_id int(10) unsigned DEFAULT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES category (id)
ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `items` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`cat_id` int unsigned DEFAULT NULL,
`parent_id` int unsigned DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `cat_id` (`cat_id`),
KEY `sub_id` (`parent_id`),
CONSTRAINT `cat_id` FOREIGN KEY (`cat_id`) REFERENCES `category` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `sub_id` FOREIGN KEY (`parent_id`) REFERENCES `category` (`parent_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
BEGIN;
INSERT INTO `category` VALUES (1, 'Colazione', NULL);
INSERT INTO `category` VALUES (2, 'Pranzo', NULL);
INSERT INTO `category` VALUES (3, 'Primi piatti', 2);
INSERT INTO `category` VALUES (4, 'Second dish', 2);
INSERT INTO `category` VALUES (5, 'Other things for lunch', 2);
COMMIT;
-- ----------------------------
-- Records of items
-- ----------------------------
BEGIN;
INSERT INTO `items` VALUES (1, 1, NULL, 'Cornetto');
INSERT INTO `items` VALUES (2, 3, 2, 'Pasta al sugo 1');
INSERT INTO `items` VALUES (3, 3, 2, 'Pasta al sugo 2');
INSERT INTO `items` VALUES (4, 3, 2, 'Pasta al sugo 3');
INSERT INTO `items` VALUES (5, 3, 2, 'Pasta al sugo 1 X');
INSERT INTO `items` VALUES (6, 3, 2, 'Pasta al sugo 2 X');
INSERT INTO `items` VALUES (7, 4, 2, 'Pasta al sugo 3 X');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
Query:
with combine_trees as (
with make_tree as (
WITH RECURSIVE category_path AS
(
SELECT id, title, parent_id
FROM category
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.title, c.parent_id
FROM category_path AS cp JOIN category AS c
ON cp.id = c.parent_id
)
SELECT cp.title, cp.id,
if(cp.id = category.id,
json_arrayagg(json_object('item_name', it.name)),
json_object(cp.title, json_object('items',json_arrayagg(json_array(json_object('item_name', it.name))))))
as tree
FROM category_path cp
INNER JOIN items it ON it.cat_id = cp.id
join category on category.id = ifnull(cp.parent_id, cp.id)
group by cp.title, cp.id, category.id
)
select json_arrayagg(json_object(title, json_array('items', tree))) output_json from make_tree group by id
)
select json_object('menu',group_concat(output_json)) as output from combine_trees;
https://sqlize.online/
The problem is that its not printing the result as JSON but its printing it formatted in one-string. How can we transform it in a JSON without that all the output is an unique string?
In your last line,
select json_object('menu',group_concat(output_json)) as output from combine_trees
you cannot use group_concat to combine the json arrays you get from the 2nd to last line (e.g. select json_arrayagg(...) output_json from make_tree group by id).
The arrays you get there each look like [...], and group_concat will give you [...], [...]. This is not a valid json array (which would need additional brackets around it, e.g. [[...],[...]]), but a string, and creating a json object from it will give you, well, that string as the value.
To combine your json arrays, you can use (as you did before) json_arrayagg instead of group_concat, e.g.
select json_object('menu',json_arrayagg(output_json)) as output from combine_trees

MySQL: query with two many to many relations and duplicates, with double intermediate table

This question is about selecting data across many-to-many relations in MySQL. Is related to another two questions, but with some differences:
MySQL: query with two many to many relations and duplicates
MySQL: query with two many to many relations and duplicates, with full data from subqueries
Those questions used a simple mockup database with simple many to many relations:
article
article_author
author
article_tag
tag
Now I will introduce next level of complexity. We want each author to be able to tag each of their articles. Thus, we will connect tags to the intermediate table article_author instead of to author directly.
article
article_author
author
article_author_tag
tag
Here is in MySQL:
CREATE TABLE `article` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `author` (
`id` INT NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
);
CREATE TABLE `tag` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `article_author` (
`id` int NOT NULL AUTO_INCREMENT,
`author_id` INT NOT NULL,
`article_id` int NOT NULL,
`createdAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_index` (`author_id`,`article_id`),
KEY `fk_article_author_author1_idx` (`author_id`),
KEY `fk_article_author_article1_idx` (`article_id`),
CONSTRAINT `fk_article_author_article1` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`),
CONSTRAINT `fk_article_author_author1` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`)
);
CREATE TABLE `article_author_tag` (
`article_author_id` int NOT NULL,
`tag_id` int NOT NULL,
PRIMARY KEY (`article_author_id`,`tag_id`),
KEY `fk_article_author_tag_article_author1_idx` (`article_author_id`),
KEY `fk_article_author_tag_tag1_idx` (`tag_id`),
CONSTRAINT `fk_article_author_tag_article_author1` FOREIGN KEY (`article_author_id`) REFERENCES `article_author` (`id`),
CONSTRAINT `fk_article_author_tag_tag1` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
);
INSERT INTO article (id, name) VALUES (1, 'first article'), (2, 'second article');
INSERT INTO `author` (id, name) VALUES (1, 'first author'), (2, 'second author');
INSERT INTO tag (id, name) VALUES (1, 'first tag'), (2, 'second tag');
INSERT INTO article_author (author_id, article_id) VALUES (1, 1), (2, 1);
INSERT INTO article_author_tag (article_author_id, tag_id) VALUES (1, 1), (1, 2), (2, 1), (2, 2);
And now, I want just to select the tags that authors of an article used to tag it, as a JSON array; but I can't get rid of duplicates:
SELECT
JSON_ARRAYAGG(tag.id)
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;
Here it is in a db<>fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=253f30ecd2f87b06c3894ef02b2ee35d
Any idea how can I get rid of them?
Edit:
I can do it with CONCAT and GROUP_CONCAT, and then casting to JSON. But it looks quite hacky:
SELECT
CAST(CONCAT('[', GROUP_CONCAT(DISTINCT tag.id SEPARATOR ','), ']') AS JSON) AS tags
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;
Here it is in a db<>fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=20087a9036acb00637be8d2f58747ba5
Any other idea will be welcome!
There is no distinct functionality for json yet (something like JSON_ARRAYAGG(distinct tag.id)), but there is a common workaround for it:
SELECT JSON_EXTRACT(JSON_OBJECTAGG(tag.id,tag.id),"$.*")
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;
JSON_OBJECTAGG works as an implict distinct, because json tags are distinct by definition, so adding {"1": 1} twice results in just one of those remaining. Afterwards, you JSON_EXTRACT just the values to get the format you intended (e.g. without the artificially added tags).
Another method would be to feed the json function with the already correct, distinct data:
SELECT JSON_ARRAYAGG(id)
FROM (
SELECT distinct tag.id
FROM article_author
JOIN article_author_tag
ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1
) subquery;
You first prepare the data the way you want it (e.g. the distinct tag-ids), then use JSON_ARRAYAGG to format your output.

How to store a key and value in a table from a JSON object in MySQL

I'm having a MySQL database tables namely ds_message and ds_params, it table ds_message contains a JSON object in each row. I would like to store the key and value of a JSON object into the table ds_params for all the records by referring the ds_message primary key id
Table: ds_message
_____________________________________________________________________________________
id key_value
_____________________________________________________________________________________
1 '{"a":"John", "b":"bat", "c":"$10"}'
2 '{"i":"Emma", "j":"Jam"}'
I'm required to insert the key_value into another table like
_________________________________________________
id message_id json_key json_value
_________________________________________________
1 1 'a' 'John'
2 1 'b' 'bat'
3 1 'c' '$10'
4 2 'i' 'Emma'
5 2 'j' 'Jam'
Table Structure:
CREATE TABLE `ds_message` (
`id` int NOT NULL,
`key_value` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='';
ALTER TABLE `ds_message`
ADD PRIMARY KEY (`id`);
INSERT INTO `ds_message` (`id`, `key_value`) VALUES
(1, '{"a":"John", "b":"bat", "c":"$10"}');
INSERT INTO `ds_message` (`id`, `key_value`) VALUES
(2, '{"i":"Emma", "j":"Jam"}');
CREATE TABLE `ds_params` (
`id` int NOT NULL,
`message_id` int NOT NULL,
`json_key` varchar(500) NOT NULL,
`json_value` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='';
ALTER TABLE `ds_params`
ADD PRIMARY KEY (`id`);
Kindly assist me how to achieve this, it may be a simple select cum insert statement or by stored procedure in an optimized way.
Use MySql JSON functions. The following information will help you writing a stored procedure which fills the table key_value from ds_message.
To get the key array of your object, use json_keys
SELECT JSON_keys('{"foo": 1, "bar": 2}')
You can get the value of each property using a key
SELECT JSON_EXTRACT('{"foo": 1, "bar": 2}', '$.foo');
Use a nested loop to iterate the ds_message records and iterate each json object using the above methods. Insert a record for each key in each object.

Duplicate entry '111-222' for key 'PRIMARY' when inserting a new value to MySQL database

I have a MySQL table running on AWS RDS with structure like the following:
CREATE TABLE `my_table` (
`col1` int(11) NOT NULL,
`col2` int(11) NOT NULL DEFAULT '0',
`f_name` varchar(45) DEFAULT NULL,
`l_name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`col1`,`col2`),
KEY `idx_col1` (`col1`),
KEY `idx_col2` (`col2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The query
SELECT * FROM my_table WHERE col1=111 AND col2=222;
returns 0 row.
But when I run an insert query
INSERT INTO my_table
(col1, col2, f_name, l_name)
VALUES (111, 222, 'John', 'Doe')
I got an error saying
Duplicate entry '111-222' for key 'PRIMARY'.
Why does this happen? The table doesn't contain a row with col1=111 and col2=222.
There's already a row with values col1=111, col2=111, f_name='John', and l_name='Doe'. But I don't think this would cause a duplicate entry error.
=========================== EDIT ======================================
There's a trigger that generates the duplicate error. Here's the script to reproduce the error.
# Initialize the tables
CREATE TABLE `my_table` (
`col1` int(11) NOT NULL,
`col2` int(11) NOT NULL DEFAULT '0',
`f_name` varchar(45) DEFAULT NULL,
`l_name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`col1`,`col2`),
KEY `idx_col1` (`col1`),
KEY `idx_col2` (`col2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `triggered_table` (
`col1` int(11) NOT NULL,
`col2` int(11) NOT NULL DEFAULT '0',
`update_date` bigint(20) DEFAULT NULL,
PRIMARY KEY (`col1`,`col2`),
KEY `idx_col1` (`col1`),
KEY `idx_col2` (`col2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# Insert the data that cause duplicate error
INSERT INTO triggered_table (col1, col2) VALUES (111, 222);
# Create the trigger
DELIMITER $$
CREATE TRIGGER weird_trigger AFTER INSERT
ON my_table
FOR EACH ROW
BEGIN
INSERT INTO triggered_table
(col1, col2)
VALUES (NEW.col1, NEW.col2);
END$$
DELIMITER ;
# Create the duplicate error
INSERT INTO my_table
(col1, col2, f_name, l_name)
VALUES (111, 222, 'John', 'Doe');
I really don't understand why the developers created the triggered_table table. Why didn't they put update_date column to my_table?
This is so weird.
All you have to do is:
Truncate your table then run (Assuming that you have only a test data but if not, you have to do some backup first)
INSERT INTO my_table
(col1, col2, f_name, l_name)
VALUES (111, 222, 'John', 'Doe')
Now if the error still exists, this is a pretty much problem.
Your error seems like you concatinated col1 and col2 as your primary key ('111-222')
You can try
select * from yourTable where FieldPrimary = '111-222' if it is already exists
The duplicate key error does not come from the my_table table but from the triggered_table table instead. When you add a row in triggered_table for the key (111, 222) and then add a new row in the my_table table (with the same key), your trigger will also try to add a new row with the key (111, 222) in your triggered_table. However there is already such a key in use and you will get the duplicate key error.
Depending on what you want to do with the my_table and triggered_table tables, you might want to change the trigger to use REPLACE INTO instead of INSERT INTO. Or you run a check with SELECT first to see if you need to add a new row or not. After that you can run an UPDATE query to change the value of update_date. But to answer your question, the duplicate key error comes from the duplicate key in the triggered_table table.