MySQL stored procedure giving different results to command line - mysql

I have a simple MySQL stored procedure designed to return all child records for a given node.
My problem is when I type this manually it returns the proper results - but when I put the same code into a Stored Procedure it returns only the parent id.
I'd really appreciate some guidance!
For example - when I call my procedure (code is below) I get:
call find_child(1006);
+--------+
| nodeid |
+--------+
| 1006 |
| 1006 |
| 1006 |
| 1006 |
+--------+
4 rows in set (0.01 sec)
BUT - When I cut and paste the command I get the proper resultset:
mysql> create temporary table KID_TABLE (nodeid INT);
Query OK, 0 rows affected (0.00 sec)
mysql> insert ignore into KID_TABLE (nodeid) select nodeid from CORPORATENODE
where parentid in (1006);
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> select * from KID_TABLE;
+--------+
| nodeid |
+--------+
| 1007 |
| 1008 |
| 1031 |
| 1038 |
+--------+
4 rows in set (0.00 sec)
Here's the code:
DELIMITER $$
DROP PROCEDURE IF EXISTS `find_child`$$
CREATE PROCEDURE `find_child`( IN NodeID INT)
DETERMINISTIC
BEGIN
declare nid INT;
set nid= NodeID;
create temporary table KID_TABLE (nodeid INT);
insert ignore into KID_TABLE (nodeid) select nodeid
from CORPORATENODE where parentid in (1006);
select * from KID_TABLE;
drop table KID_TABLE;
END $$
DELIMITER ;
Here's the DDL for PARENT table
CREATE TABLE `PARENT` (
`NODEID` int(11) NOT NULL AUTO_INCREMENT,
`PARENTID` int(11) NOT NULL DEFAULT '0' COMMENT '0 value means top node',
`NAME` varchar(50) NOT NULL,
PRIMARY KEY (`NODEID`) USING BTREE
) ENGINE=InnoDB;

Mysql will return your in variable NodeId in your select nodeid
from CORPORATENODE where parentid in (1006);
Change the variable to in_NodeID instead.
DELIMITER $$
DROP PROCEDURE IF EXISTS `find_child`$$
CREATE PROCEDURE `find_child`( IN in_NodeID INT)
BEGIN
create temporary table KID_TABLE (nodeid INT);
insert ignore into KID_TABLE (nodeid) select nodeid
from CORPORATENODE where parentid in (in_NodeID);
select * from KID_TABLE;
drop table KID_TABLE;
END $$
DELIMITER ;
But then of course, why use a temporary table?
DELIMITER $$
DROP PROCEDURE IF EXISTS `find_child`$$
CREATE PROCEDURE `find_child`( IN in_NodeID INT)
BEGIN
select nodeid from CORPORATENODE where parentid in (in_NodeID);
END $$
DELIMITER ;

Thanks Andreas et al,
I found a workaround to this just after posting - typical.
But couldn't post my solution till now.
I use Linux but have lower_case_table_names=1 set so the case is irrelevant. (I think?)
Andreas, I needed the temporary table as once I had selected back all the child records for a Node I then needed to update all the child records in the table NODEVERSION. I was running this via a trigger on the NODEVERSION table and was running into problems as I was updating the calling table etc. That's why I created a new CHILD_TABLE.
So I found that (even leaving DETERMINISTIC in... sorry pilcrow!)
The following worked as expected - all I did was added aliases ...
(The code below is a little different from above as I simplified the above code for my question)
Don't fully understand why the aliases fixed the initial problem - but it worked. Perhaps you experts know why?
CREATE PROCEDURE `find_child`( IN NodeID int)
DETERMINISTIC
BEGIN
declare nid INT;
declare rows_before INT DEFAULT 0;
declare rows_after INT DEFAULT 0;
set nid= NodeID;
delete from CHILD_TABLE;
insert into CHILD_TABLE values (nid);
set rows_before = (select count(*) from CHILD_TABLE);
if rows_before !=rows_after then
increment: repeat
set rows_before = (select count(*) from CHILD_TABLE);
insert ignore into CHILD_TABLE (nodeid) select b.nodeid from CORPORATENODE b
where b.parentid in (select c.nodeid from CHILD_TABLE c);
set rows_after= (select count(*) from CHILD_TABLE);
until rows_before =rows_after
end repeat increment;
end if;
update NODEVERSION n set STATUS=1 where n.nodeid in
(select c.nodeid from CHILD_TABLE c);
END

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;

value of variable returned from a query (with empty result set)?

I have a mysql query written as part of a Stored Procedure
-- {parameter}
SELECT id INTO emailId FROM email_list WHERE email = {email};
If this query returns an empty result set, what is the value of emailId? undefined, null, 0
I want to check if emailId contains a value and run a series of inserts.
I could use COUNT or IFNULL but that would mean declaring another variable to check against. I want to avoid it if possible.
***************************** EDIT ****************************
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `addCustomer`(
IN email1 VARCHAR(60),
IN status1 VARCHAR(15),
IN bill_pin_id SMALLINT UNSIGNED,
IN bill_addr VARCHAR(175),
IN bill_name VARCHAR(70),
IN tel VARCHAR(15)
)
BEGIN
DECLARE emailId INT UNSIGNED DEFAULT 0;
DECLARE billAddrId INT UNSIGNED DEFAULT 0;
DECLARE custId INT UNSIGNED DEFAULT 0;
DECLARE orderId INT UNSIGNED DEFAULT 0;
DECLARE sqlError TINYINT DEFAULT FALSE;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET sqlError = true;
-- get id of email if it exists
SELECT id INTO emailId FROM email_list WHERE email = email1;
-- if email exists, check if a customer is listed
IF (emailId > 0) THEN
SELECT id INTO custId FROM customer WHERE email_list_id = emailId;
IF (custId > 0) THEN
-- check for duplicate address in the address table
SELECT address_id INTO billAddrId FROM customer_addr
INNER JOIN address ON address_id = address.id
WHERE customer_id = custId AND address = bill_addr;
END IF;
END IF;
START TRANSACTION;
-- if emails isnt listed - no customer or address should exist
IF (emailId = 0) THEN
INSERT INTO email_list (email, status) VALUES (email1, status1);
SELECT LAST_INSERT_ID() INTO emailId;
SELECT emailId;
INSERT INTO customer (email_list_id, full_name, phone) VALUE (emailId, bill_name, tel);
SELECT LAST_INSERT_ID() INTO custId;
SELECT custId;
INSERT INTO address (pincode_id, address) VALUES (bill_pin_id, bill_addr);
SELECT LAST_INSERT_ID() INTO billAddrId;
SELECT billAddrId;
INSERT INTO customer_addr (address_id, customer_id) VALUES (billAddrId, custId);
SELECT LAST_INSERT_ID();
INSERT INTO orders (customer_id, order_status) VALUES (custId, 'pending');
SELECT LAST_INSERT_ID() INTO orderId;
END IF;
IF sql_error = FALSE THEN
COMMIT;
SELECT 'SUCCESS';
ELSE
ROLLBACK;
SELECT 'FAILED';
END IF;
END$$
DELIMITER ;
I removed the unnecessary lines in the code to keep it short.
Here is the values returned from the select statements. All tables are empty. But the below values indicate rows have been inserted. But it is still empty.
emailId
43
custId
12
billAddrId
13
CustomerAddr
13
orderId
8
You can test it by selecting the value:
drop procedure if exists test_proc;
create procedure test_proc()
begin
DECLARE emailId INT UNSIGNED;
SET emailId = 0;
SELECT 123 INTO emailId FROM dual WHERE 1 = 2;
SELECT emailId;
end;
call test_proc();
This will return 0, which means - the value is not changed if no row has been found.
Demo: http://rextester.com/MHRTL99697
Works for me
MariaDB [sandbox]> drop procedure if exists p;
Query OK, 0 rows affected (0.06 sec)
MariaDB [sandbox]> delimiter $$
MariaDB [sandbox]> create procedure p(inid int)
-> begin
-> declare vid int;
-> set vid = 0;
-> select id into vid from users where id = inid;
-> if vid = 1 then
-> select 'found';
-> else
-> select 'notfound';
-> end if;
-> end $$
Query OK, 0 rows affected (0.05 sec)
MariaDB [sandbox]> delimiter ;
MariaDB [sandbox]>
MariaDB [sandbox]> call p(1);
+-------+
| found |
+-------+
| found |
+-------+
1 row in set (0.02 sec)
Query OK, 0 rows affected (0.02 sec)
MariaDB [sandbox]>
MariaDB [sandbox]> call p(1000);
+----------+
| notfound |
+----------+
| notfound |
+----------+
1 row in set (0.00 sec)
Query OK, 0 rows affected, 1 warning (0.01 sec)
MariaDB [sandbox]> show warnings;
+---------+------+-----------------------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------------------+
| Warning | 1329 | No data - zero rows fetched, selected, or processed |
+---------+------+-----------------------------------------------------+
1 row in set (0.00 sec)

Mirror tables: triggers, deadlock and implicit commits

I'm have 2 similar tables, for example A and B. I want to replicate insertions in A to B, and insertions in B to A to integrate two users systems . I configured "after insert triggers" on each one. Example:
DELIMITER $$
CREATE DEFINER = `root`#`localhost` TRIGGER
`after_A_INSERT`
AFTER INSERT ON `A`
FOR EACH ROW BEGIN
INSERT INTO `B`
SET `id` = NEW.`id`,`name` = NEW.`name`;
END$$
DELIMITER ;
DELIMITER $$
CREATE DEFINER = `root`#`localhost` TRIGGER
`after_B_INSERT`
AFTER INSERT ON `B`
FOR EACH ROW BEGIN
INSERT INTO `A`
SET `id` = NEW.`id`,`name` = NEW.`name`;
END$$
DELIMITER ;
If I insert in A, the triggers calls an insert in B, but this insert executes the trigger in B and a deadlock occurs avoiding a infinite loop.
I've tried to edit triggers to DROP the another table trigger before do the INSERT and then CREATE it again after it. Example:
DELIMITER $$
CREATE DEFINER = `root`#`localhost` TRIGGER
`after_B_INSERT`
AFTER INSERT ON `B`
FOR EACH ROW BEGIN
DROP TRIGGER IF EXISTS `after_A_INSERT`;
INSERT INTO `A`
SET `id` = NEW.`id`, `name` = NEW.`name`;
/* And CREATE again here */
END$$
DELIMITER ;
However CREATE is a Data Definition Language (DDL) statement that makes an implicit commit. Thus, this can't be done.
I've tried to call a PROCEDURE with the DROP inside to handle explicitly the commit, but isn't possible too.
Any suggestion to mirror this 2 tables?
UPDATE: Using Bill Karwin suggestion, I added a origin field to each table with a respective default vale A or B. Then, I alter (DROP and reCREATE) the triggers as follows:
Trigger in A:
...
BEGIN
IF NEW.`origin`='A' THEN
INSERT INTO `B`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
Trigger in B:
...
BEGIN
IF NEW.`origin`='B' THEN
INSERT INTO `A`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
You need some way to avoid creating a cycle.
I'd suggest adding a column origin in both tables. In table A, make the DEFAULT 'A'. In table B, make the DEFAULT 'B'.
When inserting to either table in your application, always omit the origin column, allowing it to take its default value.
In both triggers, replicate to the other table only if the NEW.origin is equal to the respective table's default.
Re your comment and new error:
Sorry, I forgot to mention that in the trigger when inserting to the other table, you must also copy the value of NEW.origin. Just in your application when you do the original insert do you omit origin.
Trigger in A:
...
BEGIN
IF NEW.`origin`='A' THEN
INSERT INTO `B`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
Trigger in B:
...
BEGIN
IF NEW.`origin`='B' THEN
INSERT INTO `A`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
I created these triggers and then tested:
mysql> insert into A set name = 'bill';
Query OK, 1 row affected (0.00 sec)
mysql> insert into B set name = 'john';
Query OK, 1 row affected (0.01 sec)
mysql> select * from A;
+----+------+--------+
| id | name | origin |
+----+------+--------+
| 1 | bill | A |
| 2 | john | B |
+----+------+--------+
2 rows in set (0.00 sec)
mysql> select * from B;
+----+------+--------+
| id | name | origin |
+----+------+--------+
| 1 | bill | A |
| 2 | john | B |
+----+------+--------+

MYSQL stored procedure not checking the where clause and returning entire table

CREATE DEFINER=`root`#`localhost` PROCEDURE `Viewuser`(IN `userID` VARCHAR(50))
BEGIN
SELECT * FROM `tbl_userdetails` WHERE `UserID`=userID;
END
In the above code the user details of given user id should be return. But it returning the entire table while i execute this stored procedure.
try this method:
mysql> delimiter //
mysql> CREATE PROCEDURE simpleproc (OUT param1 INT)
-> BEGIN
-> SELECT COUNT(*) INTO param1 FROM t;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> CALL simpleproc(#a);
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #a;
+------+
| #a |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
https://dev.mysql.com/doc/refman/5.5/en/create-procedure.html
Got the answer. The field name in table should not be same as variable name.. That is the problem
CREATE DEFINER=root#localhost PROCEDURE Viewuser(IN ID VARCHAR(50))
BEGIN
SELECT * FROM tbl_userdetails WHERE UserID=ID;
END
This is working fine

Is there a way to query mySql if my connection has started a transaction?

I have found many anwsers of how frameworks handle transaction nesting, but I couldn't found if there is an executable query against mySql RDMS that returns if a transaction has started or not. Is it posible? thanks
It is a bit tricky, but you can find an example how to do this at http://forge.mysql.com/tools/tool.php?id=145
I am copying here #wonk0 link as it is no more aviable.
DELIMITER $$
CREATE PROCEDURE `myexample` (
OUT errno INT,
OUT error VARCHAR(255)
)
BEGIN
DECLARE need_to_commit BOOL DEFAULT FALSE;
main:BEGIN
-- for example, catch duplicate key errors and roll back
DECLARE EXIT HANDLER FOR 1062
BEGIN
ROLLBACK TO SAVEPOINT myexample;
SET errno = 1060,
error = 'Duplicate key.';
END;
-- catch any other errors that should cause automatic rollbacks
-- ------
-- set up the savepoint / trx handler
--
DECLARE CONTINUE HANDLER FOR 1305
BEGIN
START TRANSACTION;
SET need_to_commit = TRUE;
END;
-- this will have no effect if we are not in a trx
SAVEPOINT myexample;
-- this will error if we are not in a trx, be caught above, and start a trx
-- it will do nothing if we are already in a trx
RELEASE SAVEPOINT myexample;
-- this will always set a savepoint
-- because we are now guaranteed to be in a trx
SAVEPOINT myexample;
--
-- done setting up savepoint / trx
-- ------
-- initialize the OUT parameters
SET errno = 0,
error = '';
-- do some stuff
INSERT INTO mytable VALUES (1);
INSERT INTO yourtable VALUES (2);
-- you can even handle your own errors (without handlers!)
IF ( 0 != 1 ) THEN
ROLLBACK TO SAVEPOINT myexample;
SET errno = 1234,
error = 'Zero is not one!';
LEAVE main;
END IF;
END; -- main
-- if we were not in a transaction to start with
-- we should not leave one dangling, so commit here
IF need_to_commit THEN
COMMIT;
END IF;
END $$
DELIMITER ;
/*
EXAMPLE
mysql> create table mytable (a int primary key) engine=innodb;
Query OK, 0 rows affected (0.07 sec)
mysql> create table yourtable (b int primary key) engine=innodb;
Query OK, 0 rows affected (0.03 sec)
mysql> call myexample(#e,#r); select #e,#r;
Query OK, 0 rows affected (0.00 sec)
+------+------------------+
| #e | #r |
+------+------------------+
| 1234 | Zero is not one! |
+------+------------------+
1 row in set (0.00 sec)
mysql> select * from mytable union select * from yourtable;
Empty set (0.00 sec)
mysql> insert into yourtable values (2);
Query OK, 1 row affected (0.00 sec)
mysql> call myexample(#e,#r); select #e,#r;
Query OK, 0 rows affected (0.00 sec)
+------+----------------+
| #e | #r |
+------+----------------+
| 1060 | Duplicate key. |
+------+----------------+
1 row in set (0.00 sec)
mysql> select * from mytable union select * from yourtable;
+---+
| a |
+---+
| 2 |
+---+
1 row in set (0.00 sec)
*/