How to update multiple json object in one row? - mysql

Hello I work on JSON in MySQL. I bumped into trouble...
for example, I have data as below
{
1:{fan:0},
2:{fan:3},
3:{fan:4},
}
And I wish update every fan value to 6 : How to do this in MySQL statement?
My below statement gives "in this situation path expressions may not contain the * and ** tokens"
UPDATE mytable set json = JSON_REPLACE(json,'$.*."Fan"', 6);
Is there any simple way to achieve this ?

One option is to use a stored routine (Function). Modify the script as you need:
DROP FUNCTION IF EXISTS `update_json`;
DELIMITER //
CREATE FUNCTION `update_json` (
`json` JSON,
`key` CHAR(5),
`value` TINYINT
) RETURNS JSON
BEGIN
DECLARE `_keys` JSON DEFAULT JSON_KEYS(`json`);
DECLARE `_length_keys` TINYINT UNSIGNED DEFAULT JSON_LENGTH(`_keys`);
DECLARE `_current_key` TINYINT UNSIGNED DEFAULT 0;
DECLARE `_key` VARCHAR(10);
WHILE `_current_key` < `_length_keys` DO
SET `_key` := JSON_EXTRACT(`_keys`, CONCAT('$[', `_current_key`, ']')),
`json` := JSON_REPLACE(`json`, CONCAT('$.', `_key`,'.', `key`), `value`),
`_current_key` := `_current_key` + 1;
END WHILE;
RETURN `json`;
END//
DELIMITER ;
UPDATE `mytable`
SET `json` = `update_json`(`json`, '"fan"', 6);
See db-fiddle.

Related

Update a value and set a local variable in a case statement

I am trying to update a value in the database but also want to set a local variable VAR_IS_RATE_LIMITED. The reason for this is because I do not want to use a select statement and want to execute it within one. How can I set the VAR_IS_RATE_LIMITED? I looked at other questions but their CASE statements weren't embedded in an update statement.
DELIMITER //
CREATE FUNCTION F_RATE_LIMITED(P_IP varchar(45),
P_MAX_RATE int unsigned
)
RETURNS INT UNSIGNED
BEGIN
DECLARE VAR_IS_RATE_LIMITED INT UNSIGNED DEFAULT 0;
INSERT INTO rate_limit (ip, rate)
VALUES (P_IP, 1)
ON DUPLICATE KEY UPDATE
rate =
CASE
WHEN (rate + 1) > P_MAX_RATE THEN
SET VAR_IS_RATE_LIMITED = 1;
rate
ELSE
rate + 1
END;
RETURN VAR_IS_RATE_LIMITED;
END; //
DELIMITER ;
To set something during a data modification statement, it has to be part of an expression, not a statement. And as far as I know you can't set a stored function variable in an expression. But you can set a user variable:
DELIMITER //
CREATE FUNCTION F_RATE_LIMITED(P_IP varchar(45),
P_MAX_RATE int unsigned
)
RETURNS INT UNSIGNED
BEGIN
SET #VAR_IS_RATE_LIMITED = 0;
INSERT INTO rate_limit (ip, rate)
VALUES (P_IP, 1)
ON DUPLICATE KEY UPDATE
rate =
CASE
WHEN (rate + 1) > P_MAX_RATE THEN
CASE WHEN #VAR_IS_RATE_LIMITED := 1 THEN rate END
ELSE
rate + 1
END;
RETURN #VAR_IS_RATE_LIMITED;
END; //
DELIMITER ;
Here the variable is set in a CASE test (you can also use an IF expression) that is always true so rate is always returned from the CASE.

How to Remove Whole Index of List in a JSON

I have a JSON Values Like This.
{
"CHEQUE":[
{
"Cheque_No":"1",
"Cheque_Date":"2018-05-30",
"Cheque_Amount":"10000",
"Bank_Name":"ICICI"
},
{
"Cheque_No":"2",
"Cheque_Date":"2018-05-30",
"Cheque_Amount":"20000",
"Bank_Name":"ICICI"
},
{
"Cheque_No":"2",
"Cheque_Date":"2018-05-30",
"Cheque_Amount":"20000",
"Bank_Name":"SBI"
}
]
}'
How can i Remove Entire Index of 2 in a CHEQUE list using the search key SBI for Bank name?.
I need to Do this is in Mysql Stored Procedure without Store Values in DB. This is what I have tried:
CREATE DEFINER = root # PROCEDURE SP_TEST(
IN ls_json JSON,
OUT Message VARCHAR(1000)
)
BEGIN
SELECT
JSON_REMOVE(
ls_json,
CONCAT('$.CHEQUE[0].Bank_Name[0]')
)
INTO ls_json;
#SELECT JSON_REMOVE(ls_json,JSON_SEARCH(ls_json,'ALL',
CONCAT('$.CHEQUE[0].Cheque_No[*]')))
INTO ls_json; #select
JSON_SEARCH(ls_json, 'one', 10000);
SELECT
ls_json;
END
One option:
DELIMITER //
CREATE PROCEDURE `SP_TEST` (
IN `_ls_json` JSON,
IN `_bank_name` VARCHAR(64)
)
BEGIN
DECLARE `_base` CHAR(11) DEFAULT '$.CHEQUE[*]';
DECLARE `_base_attr` CHAR(10) DEFAULT '.Bank_Name';
DECLARE `_element` VARCHAR(1024);
`_loop`: LOOP
SET `_element` :=
JSON_SEARCH(
`_ls_json`,
'one',
`_bank_name`,
NULL,
CONCAT(`_base`, `_base_attr`)
);
IF (`_element` IS NOT NULL) THEN
SET `_ls_json` :=
JSON_REMOVE(
`_ls_json`,
JSON_UNQUOTE(
REPLACE(`_element`, `_base_attr`, '')
)
);
ELSE
LEAVE `_loop`;
END IF;
END LOOP `_loop`;
SELECT JSON_PRETTY(`_ls_json`);
END//
DELIMITER ;
See db-fiddle.

Function that generate Code returns the same things

There is a MySQL function in our web system to generate Code. The structure of the code is
district_cd(length:2) + date(length:8) + sequence no(length:5,start at 1).<like : ab2016090800001>
The sequence no was saved in table and will be updated (+1) when generate a new code.
But sometimes it returned two same codes and makes us fall in trouble. Here are the captures to replicate this problem, I will attach the DDL after this.
Step 1.Client1->change to manual commit then generate a code, but do not commit.
SET autocommit = 0;
select * from applies;
select * from sequence where apply_date = "2016-09-08";
select nextval("ab");
insert into applies (apply_id,apply_no,created,district_cd) values (2,"ab2016090800002","ab",now());
select * from sequence where apply_date = "2016-09-08";
Step2.Client2->change to manual commit then generate a code, stuck as Client1 locked
SET autocommit = 0;
select * from applies;
select * from sequence where apply_date = "2016-09-08";
insert into applies (apply_id,apply_no,created,district_cd) values (3,"ab20160908123456780","ab",now());
Step3.Client1->commit;
commit;
select * from sequence where apply_date = "2016-09-08";
Step4.Client2->code was generated and two records appeared in sequence table
select * from sequence where apply_date = "2016-09-08";
capture of Step4
Step5.Client2->commit;one of the two records that appeared in sequence table was deleted.The codes generated are duplicated.
commit;
select * from sequence where apply_date = "2016-09-08";
select * from applies;
capture of Step5
※DDL
Table:applies (apply_no:save the code)
CREATE TABLE `applies` (
`apply_id` varchar(100) NOT NULL DEFAULT '',
`apply_no` varchar(100) NOT NULL DEFAULT '',
`district_cd` varchar(100) DEFAULT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`apply_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Table:sequence (current_value:save current sequnce value)
CREATE TABLE `sequence` (
`district_cd` varchar(3) NOT NULL DEFAULT '',
`current_value` int(11) NOT NULL DEFAULT '0',
`apply_date` date NOT NULL DEFAULT '0000-00-00',
PRIMARY KEY (`district_cd`,`current_value`,`apply_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Function:currval->get current sequence value by district_cd
DELIMITER ;;
CREATE DEFINER=`usr`#`%` FUNCTION `currval`(d VARCHAR(3)) RETURNS int(11)
DETERMINISTIC
BEGIN
DECLARE value INTEGER;
DECLARE needInitSequence INTEGER;
DECLARE today DATE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET needInitSequence = 1;
SET value = 0;
SET today = current_date();
SELECT `current_value` INTO value
FROM `sequence`
WHERE `district_cd` = d AND `apply_date` = today limit 1;
IF needInitSequence = 1 THEN
INSERT INTO `sequence` (`district_cd`, `current_value`, `apply_date`) VALUES (d, value, today);
END IF;
RETURN value;
END
;;
DELIMITER ;
Function:nextval->generate code by district_cd
DELIMITER ;;
CREATE DEFINER=`usr`#`%` FUNCTION `nextval`(d VARCHAR(3)) RETURNS varchar(16) CHARSET utf8
DETERMINISTIC
BEGIN
DECLARE value INTEGER;
SET value = currval(d);
UPDATE `sequence`
SET `current_value` = `current_value` + 1
WHERE `district_cd` = d AND `apply_date` = current_date();
RETURN concat(d, date_format(now(), '%Y%m%d'), LPAD(currval(d), 5, '0'));
END
;;
DELIMITER ;
Triggers of applies->a business logic,if the length of apply_no is greater than 18,it will call the function:nextval to generate a new code
DELIMITER ;;
CREATE TRIGGER `convert_long_no` BEFORE INSERT ON `applies` FOR EACH ROW BEGIN
IF ((SELECT LENGTH(NEW.apply_no)) >= 18) THEN
SET NEW.apply_no = (SELECT nextval(NEW.district_cd));
END IF;
END
;;
DELIMITER ;
My Questions:
Why did the function:nextval returns two same codes?
Why did two records appear in sequnce when update the record.

mysql optimize stored procedure insert

This is my first stored procedure so I am not sure I am doing this correctly. I have tried to optimize this as much as I can but still end up with a query timeout at 10 minutes of running. I really need this to scale even higher than what I am working with currently. Any assistance would be great.
I have a decent sized data set (108K rows) and one of the fields contains a comma delimited list (I wish the engineers hadn't done this). I need to break apart that field so each entry is on it's own row AND all other fields are assigned to that row as well. I have developed a stored procedure that loops through the table row by row then breaks apart the field and inserts it into a second table.
Here is the code I have used:
DROP TABLE IF EXISTS dwh_inventory.nas_share_temp;
CREATE TABLE dwh_inventory.nas_share_temp (
share_id int(11) NOT NULL,
fileShareId int(11) NOT NULL,
storageId int(11) NOT NULL,
identifier varchar(1024) NOT NULL,
name varchar(255) NOT NULL,
protocol enum('CIFS','NFS') NOT NULL,
ipInterfaces VARCHAR(100) NOT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8;
DROP PROCEDURE IF EXISTS dwh_inventory.share_step;
DELIMITER $$
CREATE PROCEDURE dwh_inventory.share_step()
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE strLen INT DEFAULT 0;
DECLARE SubStrLen INT DEFAULT 0;
DECLARE ip VARCHAR(20);
SET autocommit = 0;
SELECT COUNT(*) FROM dwh_inventory.nas_share INTO n;
SET i=0;
WHILE i<n DO
SELECT id, fileShareId, storageId, identifier, name, protocol, ipInterfaces
INTO #share_id, #fileShareId, #storageId, #identifier, #name, #protocol, #ipInterfaces
FROM dwh_inventory.nas_share LIMIT i,1;
IF #ipInterfaces IS NULL THEN
SET #ipInterfaces = '';
END IF;
do_this:
LOOP
SET strLen = CHAR_LENGTH(#ipInterfaces);
SET ip = SUBSTRING_INDEX(#ipInterfaces, ',', 1);
INSERT INTO dwh_inventory.nas_share_temp
(share_id, fileShareId, storageId, identifier,name,protocol,ipInterfaces)
VALUES (#share_id,
#fileShareId,
#storageId,
#identifier,
#name,
#protocol,
ip
);
SET SubStrLen = CHAR_LENGTH(SUBSTRING_INDEX(#ipInterfaces, ',', 1)) + 2;
SET #ipInterfaces = MID(#ipInterfaces, SubStrLen, strLen);
IF #ipInterfaces = '' THEN
LEAVE do_this;
END IF;
END LOOP do_this;
COMMIT;
SET i = i + 1;
END WHILE;
SET autocommit = 1;
END;
$$
DELIMITER ;
CALL dwh_inventory.share_step();
Example of the data:
id,fileShareId,storageId,identifier,name,protocol,ipInterfaces
1325548,1128971,33309,/vol/vol0/:NFS,/vol/vol0/,NFS,"10.66.213.118,10.68.208.76"
1325549,1128991,33309,/vol/vol0/:NFS,/vol/vol0/,NFS,"10.66.213.119,10.68.208.77"
1325550,1128992,33325,/vol/aggr2_64_hs2032/EPS_ROOT/:NFS,/vol/aggr2_64_hs2032/EPS_ROOT/,NFS,10.17.124.10
1325551,1128993,33325,/vol/aggr2_64_hs2032/GCO_Report/:NFS,/vol/aggr2_64_hs2032/GCO_Report/,NFS,10.17.124.10
1325552,1128995,33325,/vol/aggr2_64_hs2032/PI/:NFS,/vol/aggr2_64_hs2032/PI/,NFS,10.17.124.10
1325553,1128996,33325,/vol/aggr2_64_hs2032/a/:NFS,/vol/aggr2_64_hs2032/a/,NFS,10.17.124.10
1325554,1128997,33325,/vol/aggr1_64_sapserv/:NFS,/vol/aggr1_64_sapserv/,NFS,147.204.2.13
1325555,1128999,33325,/vol/aggr2_64_hs2032/:NFS,/vol/aggr2_64_hs2032/,NFS,10.17.124.10
1325556,1129001,33325,/vol/aggr2_64_hs2032/central/:NFS,/vol/aggr2_64_hs2032/central/,NFS,10.17.124.10
1325557,1129004,33325,/vol/nsvfm0079b_E5V/db_clients/:NFS,/vol/nsvfm0079b_E5V/db_clients/,NFS,"10.21.188.161,10.70.151.93"
1325558,1129006,33325,/vol/aggr2_64_hs2032/istrans/:NFS,/vol/aggr2_64_hs2032/istrans/,NFS,10.17.124.10
1325559,1129008,33325,/vol/nsvfm0017_DEWDFGLD00603/:NFS,/vol/nsvfm0017_DEWDFGLD00603/,NFS,"10.21.188.115,10.70.151.138"
1325560,1129009,33325,/vol/nsvfm0017_vol0/:NFS,/vol/nsvfm0017_vol0/,NFS,"10.21.188.115,10.70.151.138"
1325561,1129011,33325,/vol/nsvfm0017a_ls2278/:NFS,/vol/nsvfm0017a_ls2278/,NFS,"10.21.188.115,10.70.151.138"
1325562,1129015,33325,/vol/nsvfm0051passive_vol0/:NFS,/vol/nsvfm0051passive_vol0/,NFS,10.17.144.249
1325563,1129017,33325,/vol/nsvfm0053_vol0/:NFS,/vol/nsvfm0053_vol0/,NFS,"10.21.189.251,10.70.151.109"
InnoDB tables must have a PRIMARY KEY.
LIMIT i,1 will get slower and slower as you go through the table -- it hast to skip over i rows before finding the one you need.
Don't try to split comma-separated text in SQL; use a real language (PHP / Perl / etc). Or, as Lew suggests, write out that column, then use LOAD DATA to bring it into another table.
LIMIT should be preceded by an ORDER BY.

How to convert TSQL query into MYSQL query?

I have developed a function for split string in tsql but mysql don't have some built in functions. I needed to function in MYSQL as i am new in mysql. Function should accept 2 parameters
1. String to be split
2. separator (',' or whatever)
Kindly reply me.
i had found solution on the internet you can into that.
DELIMITER //
DROP FUNCTION IF EXISTS `splitAndTranslate` //
CREATE FUNCTION splitAndTranslate(str TEXT, delim VARCHAR(124))
RETURNS TEXT
DETERMINISTIC
BEGIN
DECLARE i INT DEFAULT 0; -- total number of delimiters
DECLARE ctr INT DEFAULT 0; -- counter for the loop
DECLARE str_len INT; -- string length,self explanatory
DECLARE out_str text DEFAULT ''; -- return string holder
DECLARE temp_str text DEFAULT ''; -- temporary string holder
DECLARE temp_val VARCHAR(255) DEFAULT ''; -- temporary string holder for query
-- get length
SET str_len=LENGTH(str);
SET i = (LENGTH(str)-LENGTH(REPLACE(str, delim, '')))/LENGTH(delim) + 1;
-- get total number delimeters and add 1
-- add 1 since total separated values are 1 more than the number of delimiters
-- start of while loop
WHILE(ctr<i) DO
-- add 1 to the counter, which will also be used to get the value of the string
SET ctr=ctr+1;
-- get value separated by delimiter using ctr as the index
SET temp_str = REPLACE(SUBSTRING(SUBSTRING_INDEX(str, delim, ctr), LENGTH(SUBSTRING_INDEX(str, delim,ctr - 1)) + 1), delim, '');
-- query real value and insert into temporary value holder, temp_str contains the exploded ID
SELECT <real_value_column> INTO temp_val FROM <my_table> WHERE <table_id>=temp_str;
-- concat real value into output string separated by delimiter
SET out_str=CONCAT(out_str, temp_val, ',');
END WHILE;
-- end of while loop
-- trim delimiter from end of string
SET out_str=TRIM(TRAILING delim FROM out_str);
RETURN(out_str); -- return
END//
reference http://www.slickdev.com/2008/09/15/mysql-query-real-values-from-delimiter-separated-string-ids/
In mysql they they dont support some functionality like sqlserver. so spliting will be difficult in mysql
SELECT e.`studentId`, SPLIT(",", c.`courseNames`)[e.`courseId`]
FROM ..
SELECT TRIM(SUBSTRING_INDEX(yourcolumn,',',1)), TRIM(SUBSTRING_INDEX(yourcolumn,',',-1)) FROM yourtable
CREATE FUNCTION [dbo].[SplitString]
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
--Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END