MySQL Declare Exit Handler statement - mysql

SELECT #x returns 1, when it should return 2. WHY?
*Notice the DECLARE EXIT HANDLER
-- Paso 1
-- DROP DATABASE IF EXISTS test;
-- CREATE DATABASE test;
USE test;
-- Paso 2
CREATE TABLE test.t (s1 INT, PRIMARY KEY (s1));
-- Paso 3
DELIMITER $$
CREATE PROCEDURE handlerdemo ()
BEGIN
DECLARE EXIT HANDLER FOR SQLSTATE '23000'
SET #x = 1;
SELECT #x;
INSERT INTO test.t VALUES (1);
SET #x = 2;
SELECT #x;
INSERT INTO test.t VALUES (1);
SET #x = 3;
END
$$
DELIMITER ;
CALL handlerdemo();
SELECT #x;
https://dev.mysql.com/doc/refman/8.0/en/declare-handler.html
"Notice that #x is 3 after the procedure executes, which shows that execution continued to the end of the procedure after the error occurred. If the DECLARE ... HANDLER statement had not been present, MySQL would have taken the default action (EXIT) after the second INSERT failed due to the PRIMARY KEY constraint, and SELECT #x would have returned 2."

Process flow:
CALL handlerdemo();
-- DECLARE EXIT HANDLER FOR SQLSTATE '23000'
-- SET #x = 1;
-- DECLARE, none executed
SELECT #x;
-- output: NULL
INSERT INTO test.t VALUES (1);
-- table contains 1 row
SET #x = 2;
-- variable is set to 2
SELECT #x;
-- output: 2
INSERT INTO test.t VALUES (1);
-- duplicate error, handler call
-- DECLARE EXIT HANDLER FOR SQLSTATE '23000'
SET #x = 1;
-- variable is set to 1
-- EXIT stored procedure
SELECT #x;
-- output: 1

Related

MariaDB and stored procedure: Why does the cursor iterate the last row twice?

MariaDB and stored procedure: Why does the cursor iterate the last row twice using?
I have a table tab which for simplicity contains only one column id with numbers from 1 to 3 (see part 1 of the code below). I created a cursor (3) to retrieve each number from tab and entered the number as IN parameter into a short procedure (sp, part 3). The sp inserts the parameter into the table tab_ctrl (ctrl for controlling).
After runnig the cursor (4) and selecting the content of tab_ctrl I got this result:
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 3 | <- why?
+----+
The last row of tab has been iterated twice. Why the cursor doesn't stop after the third row?
-- **************************************
-- (1) Prepare tables
-- **************************************
use test0;
-- Table to read each entry
DROP TABLE IF EXISTS tab;
CREATE TABLE tab (
id INTEGER UNSIGNED DEFAULT NULL
, PRIMARY KEY (id)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
;
INSERT INTO tab VALUES
(1)
,(2)
,(3)
;
-- Table for controlling
DROP TABLE IF EXISTS tab_ctrl;
CREATE TABLE tab_ctrl (
id INTEGER UNSIGNED DEFAULT NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
;
-- **************************************
-- (4) Run sp
-- **************************************
CALL sp_cursor_add_records();
SELECT * FROM tab_ctrl;
-- **************************************
-- (2) Stored procedure
-- **************************************
DELIMITER //
DROP PROCEDURE IF EXISTS sp_add_records //
CREATE PROCEDURE sp_add_records(IN p_id INTEGER UNSIGNED)
BEGIN
INSERT INTO tab_ctrl
SELECT p_id
;
END//
DELIMITER ;
-- **************************************
-- (3) Cursor
-- **************************************
DELIMITER //
DROP PROCEDURE IF EXISTS sp_cursor_add_records //
CREATE PROCEDURE sp_cursor_add_records()
BEGIN
-- Local variables
DECLARE done BOOLEAN DEFAULT 0;
DECLARE p_id INTEGER UNSIGNED;
-- Cursor
DECLARE c CURSOR
FOR
SELECT id
FROM tab
ORDER BY id
;
-- Declare continue handler
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
-- Open cursor
OPEN c;
-- Loop through all rows
REPEAT
-- Get record
FETCH c INTO p_id;
-- Call add record procedure
CALL sp_add_records(p_id);
-- End of loop
UNTIL done END REPEAT;
-- Close cursor
CLOSE c;
END//
DELIMITER ;
If you restructure your loop to only CALL sp_ad_records(p_id) if its not done:
-- Declare continue handler
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
-- Open cursor
OPEN c;
-- Get First record
FETCH c INTO p_id;
-- Loop through all rows
WHILE NOT done DO
-- Call add record procedure
CALL sp_add_records(p_id);
-- Get next record
FETCH c INTO p_id;
-- End of loop
END WHILE;
-- Close cursor
CLOSE c;

I am getting error when creating stored procedure with while loop in mysql

i have created one stored procedure for sum of perticular condition but getting syntax error.
create table script :
CREATE TABLE count_smaller_coverage (count_records INT(11) ,block_id INT(11))
insert data :
INSERT INTO count_smaller_coverage
SELECT '114000','1' UNION
SELECT '112000','2' UNION
SELECT '98765','3' UNION
SELECT '78965','4' UNION
SELECT '4125','5' UNION
SELECT '123654','6' UNION
SELECT '78999','7' UNION
SELECT '89888','8' UNION
SELECT '99654','9' UNION
SELECT '75365','10' UNION
SELECT '25638','11' UNION
SELECT '85236','12' UNION
SELECT '65478','13' UNION
SELECT '65478','14' UNION
SELECT '85236','15'
Stored Procedure :
DELIMITER $$
DROP PROCEDURE IF EXISTS test_mysql_while_loop$$
CREATE PROCEDURE test_mysql_while_loop()
BEGIN
DECLARE strat INT;
DECLARE END INT;
DECLARE SumofCount BIGINT;
DECLARE block_id VARCHAR(2000);
SET strat=(SELECT MIN(block_id) FROM count_smaller_coverage);
SET END =(SELECT MAX(block_id) FROM count_smaller_coverage);
CREATE TABLE blocks_parts (block_id VARCHAR(2000), Counts BIGINT);
test: WHILE strat<=END DO
BEGIN
IF SumofCount > 800000 THEN
SET SumofCount=0;
SET block_id = NULL;
END IF;
SET SumofCount=COALESCE(SumofCount,0)+(SELECT count_records FROM count_smaller_coverage WHERE block_id=strat);
SELECT block_id = (COALESCE(block_id + ',', '') + CAST(block_id AS CHAR)) AS id FROM count_smaller_coverage WHERE block_id=strat;
IF SumofCount BETWEEN 800000 AND 1000000 THEN
INSERT INTO blocks_parts(block_id,Counts) VALUES (block_id,SumofCount);
END IF;
IF SumofCount BETWEEN 800000 AND 100000 THEN
LEAVE test;
END IF;
SET strat=strat+1;
END test;
END$$
DELIMITER ;
Error :
Query: CREATE PROCEDURE test_mysql_while_loop() BEGIN DECLARE strat INT; DECLARE end INT; DECLARE SumofCount BIGINT; DECLARE block_id V...
Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'test;
END' at line 33
Execution Time : 0 sec
Transfer Time : 0 sec
Total Time : 0.060 sec
You have a couple of issues in your procedure. Firstly, you have an unnecessary BEGIN after the DO in your WHILE statement. You can either remove that or match it with an END. Secondly, you need to end the WHILE loop with an END WHILE, in your case adding the test label to that statement. This should work:
test: WHILE strat<=END DO
-- BEGIN -- if you put BEGIN here ...
IF SumofCount > 800000 THEN
SET SumofCount=0;
SET block_id = NULL;
END IF;
SET SumofCount=COALESCE(SumofCount,0)+(SELECT count_records FROM count_smaller_coverage WHERE block_id=strat);
SELECT block_id = (COALESCE(block_id + ',', '') + CAST(block_id AS CHAR)) AS id FROM count_smaller_coverage WHERE block_id=strat;
IF SumofCount BETWEEN 800000 AND 1000000 THEN
INSERT INTO blocks_parts(block_id,Counts) VALUES (block_id,SumofCount);
END IF;
IF SumofCount BETWEEN 800000 AND 100000 THEN
LEAVE test;
END IF;
SET strat=strat+1;
-- END -- ... you must put END here
END WHILE test;
DELIMITER $$
USE `test`$$
DROP PROCEDURE IF EXISTS `Test`$$
CREATE DEFINER=`root`#`%` PROCEDURE `Test`()
BEGIN
SET #SumofCount=0;
SET #block_id='';
SET #Start=(SELECT MIN(block_id) FROM count_smaller_coverage);
SET #End =(SELECT MAX(block_id) FROM count_smaller_coverage);
SET #v1=5;
myloop: WHILE #Start<=#End DO
IF #SumofCount > 800000 THEN
SET #SumofCount=0;
END IF;
SET #SumofCount=(IFNULL(#SumofCount,0)+(SELECT count_records FROM count_smaller_coverage WHERE block_id=#Start));
SET #block_id = (SELECT CONCAT(#block_id ,CAST(block_id AS CHAR),',') AS id FROM count_smaller_coverage WHERE block_id=#Start);
IF #SumofCount BETWEEN 800000 AND 1000000 THEN
SET #block_id = CONCAT(LEFT(#block_id, CHAR_LENGTH(#block_id) -1), '');
INSERT INTO blocks_parts(block_id,Counts) VALUES (#block_id,#SumofCount);
SET #block_id='';
END IF;
IF #Start = #End THEN
SET #block_id = CONCAT(LEFT(#block_id, CHAR_LENGTH(#block_id) -1), '');
INSERT INTO blocks_parts(block_id,Counts) VALUES (#block_id,#SumofCount);
END IF;
SET #Start=#Start+1;
END WHILE myloop;
END$$
DELIMITER ;

Stored proc using cursor for looping and storing return value into variable

My query should while looping through cursor for table table_a check if a value exists in table_b. If a value exists in table_b then return a value into the variable #yyy.
When I run this stored proc I should get a value that returns col2,col3,col1. but it returns only col2, col3.
In this query when I'm using the into #yyy I feel its not working the way it needs to. Not sure what the problem is. Can you please help.
Just by removing into #yyy I can kind of get right results but I needs to make more changes to the variable #yyy which is why I need to store the results into it.
Delimiter $$
DROP PROCEDURE IF EXISTS sp_test3;
CREATE PROCEDURE sp_test3()
BEGIN
DECLARE DONE INT DEFAULT 0;
DECLARE col1 varchar(255);
DECLARE curA CURSOR FOR SELECT a1 FROM table_a;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET DONE = 1;
OPEN curA;
SET #SQL_TXT = '';
while done = 0 do
fetch next from CurA into col1;
if done = 0 then
SET #xxx = CONCAT("select b1 into #yyy from table_b where b1 ='",
col1,"'");
PREPARE stmt_name FROM #xxx;
EXECUTE stmt_name;
DEALLOCATE PREPARE stmt_name;
SELECT #yyy;
END IF;
END WHILE;
close curA;
end
$$
create table scripts below:
create table table_a(a1 varchar(255));
create table table_b(b1 varchar(255));
insert into table_a values('col2');
insert into table_a values('col3');
insert into table_a values('col5');
insert into table_a values('col1');
insert into table_b values('col2');
insert into table_b values('col3');
insert into table_b values('col4');
insert into table_b values('col1');
drop procedure if exists sp_test3;
drop table if exists table_b, table_a;
create table if not exists table_a(a1 varchar(255));
create table if not exists table_b(b1 varchar(255));
insert into table_a values ('col2');
insert into table_a values ('col3');
insert into table_a values ('col5');
insert into table_a values ('col1');
insert into table_b values ('col2');
insert into table_b values ('col3');
insert into table_b values ('col4');
insert into table_b values ('col1');
CREATE PROCEDURE sp_test3()
BEGIN
DECLARE DONE, DONE1 INT DEFAULT 0;
DECLARE col1 varchar(255);
DECLARE curA CURSOR FOR SELECT a1 FROM table_a;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET DONE = 1;
OPEN curA;
SET #SQL_TXT = '';
while done = 0 do
fetch next from CurA into col1;
if done = 0 then
BEGIN
DECLARE CONTINUE HANDLER FOR NOT FOUND SET DONE1 = 1;
SET #xxx = CONCAT("select b1 into #yyy
from table_b
where b1 = '", col1, "'");
PREPARE stmt_name FROM #xxx;
EXECUTE stmt_name;
DEALLOCATE PREPARE stmt_name;
if (DONE1 = 0) THEN
SELECT #yyy;
ELSE
SET DONE1 = 0;
END IF;
END;
END IF;
END WHILE;
close curA;
end;

rollback all the queries if any query returns an error in mysql database

i have two tables like
SAMPLETABLE1 (CNT INT(5));
SAMPLETABLE2 (CNT INT(5));
i have a stored procedure as follows :
create procedure p1()
begin
declare k int;
DECLARE exit handler for sqlexception
BEGIN
SET k = 0;
ROLLBACK;
END;
set autocommit = off;
START TRANSACTION;
update sampletable2 set cnt = cnt + 5;
insert into sampletable1 values ('5s');
IF k < 1
THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
end;
//
when i run this procedure it returns error "Data truncated for column 'cnt' at row 1" and first update is fired which is okay i know i am insert a character value into a number datatype.
i want my first query to be rollback if any of query return exception but it is not happening please suggest.
Create table/insert data
CREATE TABLE sampletable1
(`CNT` INT(5))
;
INSERT INTO sampletable1
(`CNT`)
VALUES
(1)
;
CREATE TABLE sampletable2
(`CNT` INT(5))
;
INSERT INTO sampletable2
(`CNT`)
VALUES
(1)
;
Query
SELECT * FROM sampletable1
Result
CNT
--------
1
Stored procedure
Ive worked here with CONTINUE HANDLER FOR SQLEXCEPTION
DELIMITER $$
CREATE PROCEDURE p1()
BEGIN
DECLARE doRollback BOOL DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET doRollback = 1;
SET autocommit = off;
START TRANSACTION;
UPDATE sampletable2 SET CNT = CNT + 5;
INSERT INTO sampletable1 (CNT) VALUES ('5s');
IF doRollback THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END$$
DELIMITER ;
Call p1()
CALL p1();
Result
1 queries executed, 0 success, 1 errors, 0 warnings
Query: CALL p1()
Error Code: 1265
Data truncated for column 'CNT' at row 1
Execution Time : 0 sec
Transfer Time : 0 sec
Total Time : 0.002 sec
Check sample1 table
SELECT * FROM sampletable1
Result
CNT
--------
1

mysql transaction - roll back on any exception

Is it possible to roll back automatically if any error occurs on a list of mysql commands?
for example something along the lines of:
begin transaction;
insert into myTable values1 ...
insert into myTable values2 ...; -- will throw an error
commit;
now, on execute i want the whole transaction to fail, and therefore i should NOT see values1 in myTable.
but unfortunately the table is being pupulated with values1 even though the transaction has errors.
any ideas how i make it to roll back? (again, on any error)?
EDIT - changed from DDL to standard SQL
You can use 13.6.7.2. DECLARE ... HANDLER Syntax in the following way:
DELIMITER $$
CREATE PROCEDURE `sp_fail`()
BEGIN
DECLARE `_rollback` BOOL DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET `_rollback` = 1;
START TRANSACTION;
INSERT INTO `tablea` (`date`) VALUES (NOW());
INSERT INTO `tableb` (`date`) VALUES (NOW());
INSERT INTO `tablec` (`date`) VALUES (NOW()); -- FAIL
IF `_rollback` THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END$$
DELIMITER ;
For a complete example, check the following SQL Fiddle.
You could use EXIT HANDLER if you for example need to SIGNAL a specific SQL EXCEPTION in your code. For instance:
DELIMITER $$
CREATE PROCEDURE `sp_fail`()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK; -- rollback any changes made in the transaction
RESIGNAL; -- raise again the sql exception to the caller
END;
START TRANSACTION;
insert into myTable values1 ...
IF fail_condition_meet THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Custom error detected.', MYSQL_ERRNO = 2000;
END IF;
insert into myTable values2 ... -- this will not be executed
COMMIT; -- this will not be executed
END$$
DELIMITER ;
The above solution are good but to make it even simpler
DELIMITER $$
CREATE PROCEDURE `sp_fail`()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK; -- rollback any error in the transaction
END;
START TRANSACTION;
insert into myTable values1 ...
insert into myTable values2 ... -- Fails
COMMIT; -- this will not be executed
END$$
DELIMITER ;