DB schema
CREATE TABLE Users(
UserId int auto_increment NOT NULL Primary key,
Username varchar(20) NOT NULL,
Password varchar(20) NOT NULL,
Email nvarchar(30) NOT NULL,
CreatedDate datetime NOT NULL,
LastLoginDate datetime NULL
);
and the stored procedure that I am trying to write is
CREATE procedure Insert_User(
in uname NVARCHAR(20),
in Pass NVARCHAR(20),
in Em NVARCHAR(30)),
out result int)
BEGIN
IF EXISTS(SELECT UserId FROM Users WHERE Username = uname)
BEGIN
set result=-1 -- Username exists.
END
elif EXISTS(SELECT UserId FROM Users WHERE Email = Em)
BEGIN
set result=-2 -- Email exists.
END
ELSE
BEGIN
INSERT INTO Users
(Username
,Password
,Email
,CreatedDate)
VALUES
(#Username
,#Password
,#Email
,GETDATE())
set result=SCOPE_IDENTITY() -- UserId
END
END
What am I am doing wrong?
You have plenty of issues in the procedure here is the one in mysql.
delimiter //
CREATE procedure Insert_User(
in uname VARCHAR(20),
in Pass VARCHAR(20),
in Em VARCHAR(30),
out result int
)
BEGIN
IF (select count(*) from Users WHERE Username = uname ) > 0 then
begin
set result=-1 ;
end ;
elseif (select count(*) from Users WHERE Email = Em ) > 0 then
begin
set result=-2 ;
end ;
else
begin
INSERT INTO Users
(Username
,Password
,Email
,CreatedDate)
VALUES
(uname
,Pass
,Em
,curdate()) ;
set result=LAST_INSERT_ID();
end ;
end if ;
END ;//
delimiter ;
Here is the test case in mysql cli
mysql> CREATE TABLE Users(
-> UserId int auto_increment NOT NULL Primary key,
-> Username varchar(20) NOT NULL,
-> Password varchar(20) NOT NULL,
-> Email nvarchar(30) NOT NULL,
-> CreatedDate datetime NOT NULL,
-> LastLoginDate datetime NULL
->
-> );
Query OK, 0 rows affected (0.16 sec)
mysql> delimiter //
mysql> CREATE procedure Insert_User(
-> in uname VARCHAR(20),
-> in Pass VARCHAR(20),
-> in Em VARCHAR(30),
-> out result int
-> )
-> BEGIN
-> IF (select count(*) from Users WHERE Username = uname ) > 0 then
-> begin
-> set result=-1 ;
-> end ;
-> elseif (select count(*) from Users WHERE Email = Em ) > 0 then
-> begin
-> set result=-2 ;
-> end ;
-> else
-> begin
-> INSERT INTO Users
-> (Username
-> ,Password
-> ,Email
-> ,CreatedDate)
-> VALUES
-> (uname
-> ,Pass
-> ,Em
-> ,curdate()) ;
->
-> set result=LAST_INSERT_ID();
-> end ;
-> end if ;
-> END ;//
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> delimiter ;
mysql> call Insert_User('Abhik','abhik','abhik#aa.com',#res);
Query OK, 1 row affected (0.05 sec)
mysql> select #res;
+------+
| #res |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> select * from Users;
+--------+----------+----------+--------------+---------------------+---------------+
| UserId | Username | Password | Email | CreatedDate | LastLoginDate |
+--------+----------+----------+--------------+---------------------+---------------+
| 1 | Abhik | abhik | abhik#aa.com | 2015-03-14 00:00:00 | NULL |
+--------+----------+----------+--------------+---------------------+---------------+
1 row in set (0.00 sec)
mysql> call Insert_User('Abhik','abhik','abhik#aa.com',#res);
Query OK, 0 rows affected (0.00 sec)
mysql> select #res;
+------+
| #res |
+------+
| -1 |
+------+
1 row in set (0.00 sec)
Related
I want to perform transactional inserts , but i do not understand the problem because i couldnt see the error. I read carefully the mysql instructions for performing transactional procedure.
The problem is there is no return out header id, How do I mitigate this issue?
CREATE PROCEDURE `add_payment`(IN `transaction_no` VARCHAR(50),
IN `transaction_type_id` MEDIUMINT(8) UNSIGNED,
IN `distributor_details_id` MEDIUMINT(8) UNSIGNED,
IN `customer_id` INT(11) UNSIGNED,
IN `amount` DECIMAL(18,8),
IN `salesman_id` INT(11) UNSIGNED,
IN `created_datetime` DATETIME,
OUT `payment_header_id` INT(11) UNSIGNED)
BEGIN
DECLARE transaction_code_id INTEGER(11) UNSIGNED DEFAULT 0;
DECLARE transaction_x_payment_header_id INTEGER(11) UNSIGNED DEFAULT 0;
DECLARE payment_details_id INTEGER(11) UNSIGNED DEFAULT 0;
DECLARE user_id MEDIUMINT(8) UNSIGNED DEFAULT 0;
#
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET payment_header_id = 0;
ROLLBACK ;
END;
START TRANSACTION;
# GET USER TO HAVE A REFERENCE WHOS DOING THIS
SET user_id = (SELECT user_id FROM salesman_x_user WHERE salesman_id = salesman_id);
# INSERT PAYMENT HEADER FIRST TO HAVE PAYMENT HEADER ID
INSERT INTO `payment_header` (`no` , `created_datetime`) VALUES(transaction_no , created_datetime);
SET payment_header_id = (SELECT LAST_INSERT_ID());
COMMIT;
END;
Avoid naming variables and parameters as columns of your tables.
See the following adapted example:
mysql> DROP TABLE IF EXISTS `payment_header`;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE IF NOT EXISTS `payment_header`(
-> `payment_header_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> `no` VARCHAR(50),
-> `created_datetime` DATETIME,
-> UNIQUE KEY (`no`)
-> );
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER //
mysql> DROP PROCEDURE IF EXISTS `add_payment`//
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE PROCEDURE `add_payment` (
-> IN `_transaction_no` VARCHAR(50),
-> IN `_transaction_type_id` MEDIUMINT(8) UNSIGNED,
-> IN `_distributor_details_id` MEDIUMINT(8) UNSIGNED,
-> IN `_customer_id` INT(11) UNSIGNED,
-> IN `_amount` DECIMAL(18, 8),
-> IN `_salesman_id` INT(11) UNSIGNED,
-> IN `_created_datetime` DATETIME,
-> OUT `_payment_header_id` INT(11) UNSIGNED
-> )
-> BEGIN
-> DECLARE `transaction_code_id` INTEGER(11) UNSIGNED DEFAULT 0;
-> DECLARE `transaction_x_payment_header_id` INTEGER(11) UNSIGNED DEFAULT 0;
-> DECLARE `payment_details_id` INTEGER(11) UNSIGNED DEFAULT 0;
-> DECLARE `user_id` MEDIUMINT(8) UNSIGNED DEFAULT 0;
->
-> DECLARE EXIT HANDLER FOR SQLEXCEPTION
-> BEGIN
-> SET `_payment_header_id` = 0;
-> ROLLBACK;
-> END;
->
-> START TRANSACTION;
-> # GET USER TO HAVE A REFERENCE WHOS DOING THIS
-> /*SET `user_id` = (SELECT `user_id`
/*> FROM `salesman_x_user`
/*> WHERE `salesman_id` = `_salesman_id`);*/
->
-> # INSERT PAYMENT HEADER FIRST TO HAVE PAYMENT HEADER ID
-> INSERT INTO `payment_header` (`no` , `created_datetime`)
-> VALUES (`_transaction_no`, `_created_datetime`);
->
-> SET `_payment_header_id` = LAST_INSERT_ID();
-> COMMIT;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> CALL `add_payment`(
-> '00000000000000000000000000000000000000000000000001',
-> NULL,
-> NULL,
-> NULL,
-> NULL,
-> NULL,
-> NOW(),
-> #`payment_header_id`
-> );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #`payment_header_id`;
+----------------------+
| #`payment_header_id` |
+----------------------+
| 1 |
+----------------------+
1 row in set (0.00 sec)
mysql> SELECT
-> `payment_header_id`,
-> `no`,
-> `created_datetime`
-> FROM
-> `payment_header`;
+-------------------+----------------------------------------------------+---------------------+
| payment_header_id | no | created_datetime |
+-------------------+----------------------------------------------------+---------------------+
| 1 | 00000000000000000000000000000000000000000000000001 | 2000-01-01 00:00:01 |
+-------------------+----------------------------------------------------+---------------------+
1 row in set (0.00 sec)
mysql> CALL `add_payment`(
-> '00000000000000000000000000000000000000000000000001',
-> NULL,
-> NULL,
-> NULL,
-> NULL,
-> NULL,
-> NOW(),
-> #`payment_header_id`
-> );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #`payment_header_id`;
+----------------------+
| #`payment_header_id` |
+----------------------+
| 0 |
+----------------------+
1 row in set (0.00 sec)
mysql> SELECT
-> `payment_header_id`,
-> `no`,
-> `created_datetime`
-> FROM
-> `payment_header`;
+-------------------+----------------------------------------------------+---------------------+
| payment_header_id | no | created_datetime |
+-------------------+----------------------------------------------------+---------------------+
| 1 | 00000000000000000000000000000000000000000000000001 | 2000-01-01 00:00:01 |
+-------------------+----------------------------------------------------+---------------------+
1 row in set (0.00 sec)
See db-fiddle.
This is my code:
IF EXISTS(SELECT * FROM User WHERE Username = #Username) THEN
RETURN -1;
ELSEIF EXISTS(SELECT * FROM User WHERE Email = #Email)
THEN
RETURN -2;
ELSE
INSERT INTO User(Username, Password, Email)
VALUES ('Nicki',#Password,#Email);
RETURN LAST_INSERT_ID();
END IF
And an image
But the function doesn't insert a value — can you explain why not?
#Username is a 9.4 User-Defined Variables and Username is a table column.
Also, check: C.1 Restrictions on Stored Programs::Name Conflicts within Stored Routines.
Try:
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 5.7.18 |
+-----------+
1 row in set (0.00 sec)
mysql> DROP FUNCTION IF EXISTS `InsertUser`;
Query OK, 0 rows affected (0.00 sec)
mysql> DROP TABLE IF EXISTS `User`;
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE TABLE IF NOT EXISTS `User`(
-> `Id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
-> `Username` VARCHAR(255) NOT NULL,
-> `Password` VARCHAR(255) NOT NULL,
-> `Email` VARCHAR(255) NOT NULL
-> );
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER //
mysql> CREATE FUNCTION `InsertUser`(`_Username` VARCHAR(255),
-> `_Email` VARCHAR(255),
-> `_Password` VARCHAR(255)
-> )
-> RETURNS INT
-> LANGUAGE SQL
-> DETERMINISTIC
-> MODIFIES SQL DATA
-> BEGIN
-> IF EXISTS(SELECT * FROM `User` WHERE `Username` = `_Username`) THEN
-> RETURN -1;
-> ELSEIF EXISTS(SELECT * FROM `User` WHERE `Email` = `_Email`) THEN
-> RETURN -2;
-> ELSE
-> INSERT INTO `User` (`Username`, `Password`, `Email`)
-> VALUES ('Nicki', `_Password`, `_Email`);
-> RETURN LAST_INSERT_ID();
-> END IF;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> SELECT
-> `Id`,
-> `Username`,
-> `Password`,
-> `Email`
-> FROM
-> `User`;
Empty set (0.00 sec)
mysql> SELECT `InsertUser`(NULL, 'pass', 'email#domain.ext');
+------------------------------------------------+
| `InsertUser`(NULL, 'pass', 'email#domain.ext') |
+------------------------------------------------+
| 1 |
+------------------------------------------------+
1 row in set (0.01 sec)
mysql> SELECT
-> `Id`,
-> `Username`,
-> `Password`,
-> `Email`
-> FROM
-> `User`;
+----+----------+------------------+-------+
| Id | Username | Password | Email |
+----+----------+------------------+-------+
| 1 | Nicki | email#domain.ext | pass |
+----+----------+------------------+-------+
1 row in set (0.00 sec)
mysql> SELECT `InsertUser`(NULL, 'pass', 'email#domain.ext');
+------------------------------------------------+
| `InsertUser`(NULL, 'pass', 'email#domain.ext') |
+------------------------------------------------+
| -2 |
+------------------------------------------------+
1 row in set (0.00 sec)
Example db-fiddle.
I have a MySQL database with a table inventory with multiple triggers set up to capture changes in a second table inventory_history. I'm updating two of the fields (both in a single query, and in two separate queries), and the trigger consistently works on only one of the two fields (qty but not on last_sale).
Here is the troublesome query:
UPDATE inventory SET last_sale = 321, qty = 0 WHERE id = 123;
Alternately, these query combinations don't work either:
UPDATE inventory SET last_sale = 321 WHERE id = 123;
UPDATE inventory SET qty = 0 WHERE id = 123;
Here are the table constructs and triggers:
CREATE TABLE `inventory` (
`serial_no` varchar(255) DEFAULT NULL,
`qty` mediumint(9) DEFAULT NULL,
`partid` mediumint(9) unsigned DEFAULT NULL,
`last_sale` mediumint(9) unsigned DEFAULT NULL,
`date_created` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`id` mediumint(9) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `partid` (`partid`),
KEY `date_created` (`date_created`),
KEY `last_sale` (`last_sale`),
KEY `last_rma` (`last_return`),
KEY `last_purchase` (`last_purchase`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `inventory_history` (
`date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`inventory_id` mediumint(9) unsigned NOT NULL,
`field_changed` enum('serial_no','qty','partid','last_sale','new') NOT NULL,
`changed_from` varchar(255) NOT NULL,
KEY `inventory_id` (`inventory_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TRIGGER `inv_new` AFTER INSERT ON `inventory`
FOR EACH ROW BEGIN
SET
#id = NEW.id,
#userid = NEW.userid,
#date = now();
INSERT INTO inventory_history VALUES (#date,#userid, #id, 'new', 'new');
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `inv_update`;
DELIMITER //
CREATE TRIGGER `inv_update` AFTER UPDATE ON `inventory`
FOR EACH ROW BEGIN
SET #userid = OLD.userid;
SET #inv_id = OLD.id;
IF (OLD.serial_no <> NEW.serial_no) THEN
INSERT INTO inventory_history VALUES (now(), #userid, #inv_id, 'serial_no', OLD.serial_no);
END IF;
IF (OLD.qty <> NEW.qty) THEN
INSERT INTO inventory_history VALUES (now(), #userid, #inv_id, 'qty', OLD.qty);
END IF;
IF (OLD.partid <> NEW.partid) THEN
INSERT INTO inventory_history VALUES (now(), #userid, #inv_id, 'partid', OLD.partid);
END IF;
IF (OLD.last_sale <> NEW.last_sale) THEN
INSERT INTO inventory_history VALUES (now(), #userid, #inv_id, 'last_sale', OLD.last_sale);
END IF;
END
//
DELIMITER ;
So again, the qty trigger works, but the last_sale does not.
I can't reproduce the problem:
mysql> DROP TABLE IF EXISTS `inventory_history`, `inventory`;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `inventory` (
-> `serial_no` varchar(255) DEFAULT NULL,
-> `qty` mediumint(9) DEFAULT NULL,
-> `partid` mediumint(9) unsigned DEFAULT NULL,
-> `last_sale` mediumint(9) unsigned DEFAULT NULL,
-> `date_created` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
-> `id` mediumint(9) unsigned NOT NULL AUTO_INCREMENT,
-> PRIMARY KEY (`id`),
-> KEY `partid` (`partid`),
-> KEY `date_created` (`date_created`),
-> KEY `last_sale` (`last_sale`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `inventory_history` (
-> `date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-> `inventory_id` mediumint(9) unsigned NOT NULL,
-> `field_changed` enum('serial_no', 'qty', 'partid', 'last_sale', 'new') NOT NULL,
-> `changed_from` varchar(255) NOT NULL,
-> KEY `inventory_id` (`inventory_id`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)
mysql> DELIMITER //
mysql> CREATE TRIGGER `inv_new` AFTER INSERT ON `inventory`
-> FOR EACH ROW
-> BEGIN
-> DECLARE `_id` MEDIUMINT UNSIGNED DEFAULT NEW.`id`;
-> DECLARE `_date` TIMESTAMP DEFAULT NOW();
-> INSERT INTO `inventory_history` VALUES (`_date`, `_id`, 'new', 'new');
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> DROP TRIGGER IF EXISTS `inv_update`;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> DELIMITER //
mysql> CREATE TRIGGER `inv_update` AFTER UPDATE ON `inventory`
-> FOR EACH ROW
-> BEGIN
-> DECLARE `_inv_id` MEDIUMINT UNSIGNED DEFAULT OLD.`id`;
->
-> IF (OLD.`serial_no` <> NEW.`serial_no`) THEN
-> INSERT INTO `inventory_history` VALUES (NOW(), `_inv_id`, 'serial_no', OLD.`serial_no`);
-> END IF;
->
-> IF (OLD.`qty` <> NEW.`qty`) THEN
-> INSERT INTO `inventory_history` VALUES (NOW(), `_inv_id`, 'qty', OLD.`qty`);
-> END IF;
->
-> IF (OLD.`partid` <> NEW.`partid`) THEN
-> INSERT INTO `inventory_history` VALUES (NOW(), `_inv_id`, 'partid', OLD.`partid`);
-> END IF;
->
-> IF (OLD.`last_sale` <> NEW.`last_sale`) THEN
-> INSERT INTO `inventory_history` VALUES (NOW(), `_inv_id`, 'last_sale', OLD.`last_sale`);
-> END IF;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> INSERT INTO `inventory`
-> (`serial_no`, `qty`, `partid`, `last_sale`)
-> VALUES
-> ('1', 0, 0, 321);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT
-> `serial_no`,
-> `qty`,
-> `partid`,
-> `last_sale`,
-> `date_created`,
-> `id`
-> FROM
-> `inventory`;
+-----------+------+--------+-----------+---------------------+----+
| serial_no | qty | partid | last_sale | date_created | id |
+-----------+------+--------+-----------+---------------------+----+
| 1 | 0 | 0 | 321 | 2016-11-15 00:00:51 | 1 |
+-----------+------+--------+-----------+---------------------+----+
1 row in set (0.00 sec)
mysql> SELECT
-> `date`,
-> `inventory_id`,
-> `field_changed`,
-> `changed_from`
-> FROM
-> `inventory_history`;
+---------------------+--------------+---------------+--------------+
| date | inventory_id | field_changed | changed_from |
+---------------------+--------------+---------------+--------------+
| 2016-11-15 00:00:51 | 1 | new | new |
+---------------------+--------------+---------------+--------------+
1 row in set (0.00 sec)
mysql> UPDATE `inventory`
-> SET `last_sale` = 0, `qty` = 321
-> WHERE `id` = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT
-> `date`,
-> `inventory_id`,
-> `field_changed`,
-> `changed_from`
-> FROM
-> `inventory_history`;
+---------------------+--------------+---------------+--------------+
| date | inventory_id | field_changed | changed_from |
+---------------------+--------------+---------------+--------------+
| 2016-11-15 00:00:51 | 1 | new | new |
| 2016-11-15 00:00:51 | 1 | qty | 0 |
| 2016-11-15 00:00:51 | 1 | last_sale | 321 |
+---------------------+--------------+---------------+--------------+
3 rows in set (0.00 sec)
mysql> SELECT
-> `serial_no`,
-> `qty`,
-> `partid`,
-> `last_sale`,
-> `date_created`,
-> `id`
-> FROM
-> `inventory`;
+-----------+------+--------+-----------+---------------------+----+
| serial_no | qty | partid | last_sale | date_created | id |
+-----------+------+--------+-----------+---------------------+----+
| 1 | 321 | 0 | 0 | 2016-11-15 00:00:51 | 1 |
+-----------+------+--------+-----------+---------------------+----+
1 row in set (0.00 sec)
Days later, turns out to be a simple but profound explanation (isn't it always?).
The trigger statement I used above was:
IF (OLD.last_sale <> NEW.last_sale) THEN
INSERT INTO inventory_history VALUES (now(), #userid, #inv_id, 'last_sale', OLD.last_sale);
END IF;
The problem with this statement is that it doesn't capture a value change from NULL to 0, for example. It only captures a value to another value. So I needed to ADD a second statement for the NULL scenarios:
IF (OLD.last_sale IS NULL AND NEW.last_sale IS NOT NULL) THEN
INSERT INTO inventory_history VALUES (now(), #userid, #inv_id, 'last_sale', OLD.last_sale);
END IF;
And all the world is right. :)
I am using MySql stored procedure to do operation with my database.
Here is sample store procedure.
CREATE DEFINER=`ntadmin`#`%` PROCEDURE `usp_user`(
IN cMode VARCHAR(20),
IN nUserID MEDIUMINT UNSIGNED,
IN cEmail VARCHAR(50),
IN cFirstName VARCHAR(20),
IN cLastName VARCHAR(20),
IN nIsMale TINYINT(1) UNSIGNED
) DETERMINISTIC
BEGIN
IF (cMode = "insert") THEN
IF NOT EXISTS(SELECT 1 FROM user WHERE email = cEmail) THEN
INSERT INTO user(email, firstname, lastname, ismale)
VALUES (cEmail, cFirstName, cLastName, nIsMale);
SET nUserID = LAST_INSERT_ID();
-- Here I would like to send all details of inserted user.
SET max_sp_recursion_depth = 1;
CALL usp_user("select", nUserID, null, null, null, null);
ELSE
SELECT -1 AS "UnSuccess", "Email already exists" AS "Error"; ;
END IF;
ELSEIF (cMode = "select") THEN
SELECT email, firstname, lastname, ismale
FROM user
WHERE userid = nUserID;
ELSEIF (cMode = "delete") THEN
//delete code
SELECT 1 AS "Success";
END IF;
END
As you look carefully my code, I am calling this stored procedure recursively in insert using max_sp_recursion_depth. Is there any better option to avoid recursive call?
Thanks.
Should I use Label or GOTO statements?
One option to avoid recursion is changing the structure as follows:
mysql> DELIMITER //
mysql> DROP FUNCTION IF EXISTS `existsEmail`//
Query OK, 0 rows affected (0.00 sec)
mysql> DROP PROCEDURE IF EXISTS `usp_user_delete`//
Query OK, 0 rows affected (0.00 sec)
mysql> DROP PROCEDURE IF EXISTS `usp_user_insert`//
Query OK, 0 rows affected (0.00 sec)
mysql> DROP PROCEDURE IF EXISTS `usp_user_select`//
Query OK, 0 rows affected (0.00 sec)
mysql> DROP TABLE IF EXISTS `user`//
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE IF NOT EXISTS `user` (
-> `nuserid` MEDIUMINT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-> `firstname` VARCHAR(20) DEFAULT NULL,
-> `lastname` VARCHAR(20) DEFAULT NULL,
-> `email` VARCHAR(50) DEFAULT NULL,
-> `ismale` TINYINT(1) UNSIGNED DEFAULT NULL,
-> PRIMARY KEY (`nuserid`),
-> UNIQUE KEY `idx_email` (`email`)
-> )//
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE PROCEDURE `usp_user_insert`(
-> IN `cEmail` VARCHAR(50),
-> IN `cFirstName` VARCHAR(20),
-> IN `cLastName` VARCHAR(20),
-> IN `nIsMale` TINYINT(1) UNSIGNED
-> )
-> BEGIN
-> DECLARE `_existsEmail` CONDITION FOR SQLSTATE '45000';
-> IF (SELECT NOT `existsEmail`(`cEmail`)) THEN
-> INSERT INTO `user` (
-> `email`,
-> `firstname`,
-> `lastname`,
-> `ismale`
-> ) VALUES (
-> `cEmail`,
-> `cFirstName`,
-> `cLastName`,
-> `nIsMale`
-> );
-> CALL `usp_user_select`(LAST_INSERT_ID());
-> ELSE
-> -- SELECT -1 'UnSuccess', 'Email already exists' 'Error';
-> SIGNAL `_existsEmail`
-> SET MESSAGE_TEXT = 'Email already exists', MYSQL_ERRNO = 4000;
-> END IF;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE PROCEDURE `usp_user_select`(
-> IN `cnuserid` MEDIUMINT UNSIGNED
-> )
-> BEGIN
-> SELECT
-> `email`,
-> `firstname`,
-> `lastname`,
-> `ismale`
-> FROM
-> `user`
-> WHERE
-> `nuserid`= `cnuserid`;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE PROCEDURE `usp_user_delete`()
-> BEGIN
-> -- delete code
-> SELECT 1 'Success';
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE FUNCTION `existsEmail`(
-> `cemail` VARCHAR(50)
-> ) RETURNS TINYINT(1)
-> BEGIN
-> RETURN
-> EXISTS(
-> SELECT
-> NULL
-> FROM
-> `user`
-> WHERE
-> `email` = `cemail`
-> );
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> CALL `usp_user_insert`(
-> 'user#example.com',
-> 'firstname_user',
-> 'lastname_user',
-> 1
-> )//
+------------------+----------------+---------------+--------+
| email | firstname | lastname | ismale |
+------------------+----------------+---------------+--------+
| user#example.com | firstname_user | lastname_user | 1 |
+------------------+----------------+---------------+--------+
1 row in set (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
mysql> CALL `usp_user_insert`(
-> 'user#example.com',
-> 'firstname_user',
-> 'lastname_user',
-> 1
-> )//
ERROR 4000 (45000): Email already exists
I'm creating a stored function which should insert new row to table. In this table is also one unique column.
How can I check if everything goes well and row really was inserted?
How can I check exactly that it's this unique column found (for example - try to add duplicate value)?
You can check the LAST_INSERT_ID() function and INSERT IGNORE.
If the INSERT IGNORE was successful, you get the primary key returned. Let's create a table with an auto increment primary key and a unique key on a name.
use test
DROP TABLE IF EXISTS nametable;
CREATE TABLE nametable
(
id int not null auto_increment,
name varchar(20) not null,
primary key (id),
unique key (name)
);
DELIMITER $$
DROP FUNCTION IF EXISTS `test`.`InsertName` $$
CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
BEGIN
INSERT IGNORE INTO test.nametable (name) VALUES (newname);
RETURN LAST_INSERT_ID();
END $$
DELIMITER ;
SELECT InsertName('rolando');
SELECT InsertName('rolando');
SELECT InsertName('pamela');
SELECT InsertName('pamela');
SHOW CREATE TABLE test.nametable\G
SELECT * FROM test.nametable;
Here is the example being run:
mysql> use test
Database changed
mysql> DROP TABLE IF EXISTS nametable;
Query OK, 0 rows affected (0.04 sec)
mysql> CREATE TABLE nametable
-> (
-> id int not null auto_increment,
-> name varchar(20) not null,
-> primary key (id),
-> unique key (name)
-> );
Query OK, 0 rows affected (0.07 sec)
mysql> DELIMITER $$
mysql> DROP FUNCTION IF EXISTS `test`.`InsertName` $$
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
-> BEGIN
-> INSERT IGNORE INTO test.nametable (name) VALUES (newname);
-> RETURN LAST_INSERT_ID();
-> END $$
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.03 sec)
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 0 |
+-----------------------+
1 row in set (0.02 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 3 |
+----------------------+
1 row in set (0.02 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 0 |
+----------------------+
1 row in set (0.03 sec)
mysql> SHOW CREATE TABLE test.nametable\G
*************************** 1. row ***************************
Table: nametable
Create Table: CREATE TABLE `nametable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> SELECT * FROM test.nametable;
+----+---------+
| id | name |
+----+---------+
| 3 | pamela |
| 1 | rolando |
+----+---------+
2 rows in set (0.00 sec)
mysql>
As shown in the preceding example, you can check the return value of the function. A nonzero return value means the INSERT IGNORE went well. A zero return value indicates a duplicate key without introducing an error number to the mysqld.
The drawback to this approach is that you cannot go back and use id 2 and 4 because of failed attempts to INSERT IGNORE in the event of a duplicate key.
Let's try another example with a different stored function setup using INSERT and without using LAST_INSERT_ID():
use test
DROP TABLE IF EXISTS nametable;
CREATE TABLE nametable
(
id int not null auto_increment,
name varchar(20) not null,
primary key (id),
unique key (name)
);
DELIMITER $$
DROP FUNCTION IF EXISTS `test`.`InsertName` $$
CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
BEGIN
DECLARE rv INT;
SELECT COUNT(1) INTO rv FROM test.nametable WHERE name = newname;
IF rv = 0 THEN
INSERT INTO test.nametable (name) VALUES (newname);
END IF;
RETURN rv;
END $$
DELIMITER ;
SELECT InsertName('rolando');
SELECT InsertName('rolando');
SELECT InsertName('pamela');
SELECT InsertName('pamela');
SHOW CREATE TABLE test.nametable\G
SELECT * FROM test.nametable;
Here is the result:
mysql> use test
Database changed
mysql> DROP TABLE IF EXISTS nametable;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> CREATE TABLE nametable
-> (
-> id int not null auto_increment,
-> name varchar(20) not null,
-> primary key (id),
-> unique key (name)
-> );
Query OK, 0 rows affected (0.10 sec)
mysql> DELIMITER $$
mysql> DROP FUNCTION IF EXISTS `test`.`InsertName` $$
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
-> BEGIN
-> DECLARE rv INT;
-> SELECT COUNT(1) INTO rv FROM test.nametable WHERE name = newname;
-> IF rv = 0 THEN
-> INSERT INTO test.nametable (name) VALUES (newname);
-> END IF;
-> RETURN rv;
-> END $$
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 0 |
+-----------------------+
1 row in set (0.04 sec)
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.00 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 0 |
+----------------------+
1 row in set (0.03 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 1 |
+----------------------+
1 row in set (0.00 sec)
mysql> SHOW CREATE TABLE test.nametable\G
*************************** 1. row ***************************
Table: nametable
Create Table: CREATE TABLE `nametable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> SELECT * FROM test.nametable;
+----+---------+
| id | name |
+----+---------+
| 2 | pamela |
| 1 | rolando |
+----+---------+
2 rows in set (0.00 sec)
mysql>
In this example, the stored function returns 0 if the INSERT was OK, and returns 1 with a duplicate key on the name. The advantage? No wasted id numbers for auto_increment. The disadvantage? Doing a SELECT statement each time to check for the name already being present in the table.
You have a choice as to which way you want to handle duplicate keys. The first method lets mysqld handle the condition of the INSERT IGNORE. The second method has the stored function checking for the duplicate key first before the INSERT.
Stored Procedures are "all-or-nothing"
Therefore, if you include an INSERT in the sproc, and that INSERT fails on a duplicate key error, the entire sproc will be rolled back.
If the sproc executes without error, you can be confident that the INSERT did not have an error. Now, this does not mean the INSERT actually happens just because the sproc completes, just that there were no errors.... for example, if you had some WHERE clause which excludes the INSERT but doesn't throw an error then there may be some ambiguity.