UNIQUE constraint in MySQL rows - mysql

I am performing an INSERT query in my database as such:
$query = "INSERT IGNORE INTO user VALUES ('', 0, $safe_email, '$hashed_password')";
$result = $db->query($query);
The 3rd row in the db table is email which I have set a unique constraint to.
If I try to insert a duplicate email, with the above query notice I have the INSERT IGNORE which won't insert the record if it is a duplicate to one that already exists in the db, however it won't give an error or any kind of indication that a duplicate record is trying be inserted.
I want to show a nice error message if a duplicate is found but with the INSERT IGNORE I am struggling to do this because it won't show an error it will just ignore the query.
So I need something like:
IF (duplicate entry found in db){
echo "User already exists";
}
END IF

Use normal insert query and implement the query in try-catch statement. 'Insert' query will fail if you try to insert same email since it is unique constraint. So you can catch the exception as the 'Insert' query fails.
Example:
try {
"your insert query";
} catch (Exception $e) {
"your insert failure exception"
}
NB: you can catch all the exceptions that occurred during the execution of insert query which will be more helpful

As per documentation on INSERT ... IGNORE:
If you use the IGNORE keyword, errors that occur while executing the INSERT statement are ignored. For example, without IGNORE, a row that duplicates an existing UNIQUE index or PRIMARY KEY value in the table causes a duplicate-key error and the statement is aborted. With IGNORE, the row still is not inserted, but no error occurs. Ignored errors may generate warnings instead, although duplicate-key errors do not.
You can issue a show warnings; or any compatible similar statement from your scripting language or SQL interface. If it returns one or more such warnings, may be one of them can be on such insertion issues. Using them, you can show proper error or consoling message to the end user.
Edit 1:
... but ... how do I throw my own error message instead of the default exception when using INSERT without the IGNORE.
You can define a BEFORE INSERT trigger to identify duplicate data row and throw custom error message when found one.
Example:
delimiter //
drop trigger if exists bi_table_trigger //
CREATE TRIGGER bi_table_trigger BEFORE INSERT ON table
FOR EACH ROW
BEGIN
declare rowCount int default 0;
declare error_message varchar(1024) default '';
SELECT COUNT(1) into rowCount FROM table
WHERE email = NEW.email;
IF ( rowCount > 0 ) THEN -- if( rowCount ) -- too works
set error_message =
concat( error_message, 'User with email \'' );
set error_message =
concat( error_message, NEW.email, '\' ' );
set error_message =
concat( error_message, 'already exists' );
-- throw the error
-- User with email '?' already exists ( example )
signal sqlstate 1062 set message_text = error_message;
END IF;
END;//
delimiter ;

Related

Syntax error with IF EXISTS UPDATE ELSE INSERT (NODE JS)

I am trying to write an SQL query that will update an entry if it exists and insert a new one if it does not exist. The UPDATE ON DUPLICATE KEY option doesn't work because I am not querying by the primary key. The SQL statement I am referring to is in the sql.query() function below. I have also added the error message.
Asset.create = (newAsset, result) => {
sql.query(
`if exists(SELECT * from asset WHERE
AssetId="${newAsset.AssetId}" AND
AccountID="${newAsset.AccountId}") BEGIN UPDATE asset set
Amount="${newAsset.Amount}" where AssetId="${newAsset.AssetId}"
AND AccountID="${newAsset.AccountId}" End else begin INSERT
INTO asset SET ? end`,
newAsset,
(err, res) => {
if (err) {
console.log("error", err);
result(err, null);
return;
}
result(null, { id: res.insertId, ...newAsset });
}
);
};
Error message:
sqlMessage: 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 'if exists(SELECT * from asset WHERE AssetId="bitcoin" AND AccountID="2c341fed-cf' at line 1,
In MySQL, compound statement syntax such as BEGIN ... END and IF ... THEN ... END IF is supported only in stored routines. You can't use such statements when executing them directly from clients.
https://dev.mysql.com/doc/refman/8.0/en/sql-compound-statements.html says:
This section describes the syntax for the BEGIN ... END compound statement and other statements that can be used in the body of stored programs: Stored procedures and functions, triggers, and events.
In the example you show, you seem to be trying to update a row, and if the row does not exist, then insert it.
One solution if you have a primary key or unique key on the table is to use INSERT ON DUPLICATE KEY UPDATE:
INSERT INTO asset (AssetId, AccountId, Amount) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE Amount = VALUES(Amount);
I'm supposing for the above example that AssetId and AccountId are the primary key.
An alternative is to try the UPDATE first, and in the callback, check result.affectedRows. If this is zero, then try the INSERT.
this is no valid sql code
In SQL you would write something like
SELECT
if (exists(SELECT * from asset WHERE AssetId="${newAsset.AssetId}" AND AccountID="${newAsset.AccountId}")
, #sql := 'UPDATE asset set Amount="${newAsset.Amount}" where AssetId="${newAsset.AssetId}" AND AccountID="${newAsset.AccountId}" ',
#sql := 'INSERT INTO asset SET ? ');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
but this is multi query which mus be eanabled
it needs still some work ys you have to many quotes

Deleting row on trigger before insert, with on duplicate update

Situation
I am saving modifications done on users' profiles (for a logging purpose).
I am using this query to insert/update in a :
INSERT INTO infos_update (username, date_modif, column_name, old_value, new_value)
VALUES ('johnsmith', CURDATE(), 'department', 'Management', 'IT/IS')
ON DUPLICATE KEY UPDATE new_value='IT/IS, date_modif=CURDATE()
The primary key is composed of date_modif, username, column_name, where date is a date and not a datetime.
Goal
I want to use a trigger to avoid logging values that did not changed (where old_value and new_value are equals)
delimiter //
CREATE TRIGGER before_insert_infos_update
BEFORE INSERT ON infos_update FOR EACH ROW
BEGIN
IF NEW.old_value = NEW.new_value THEN
DELETE FROM infos_update
WHERE infos_update.username= NEW.username
AND infos_update.date_modif = NEW.date_modif
AND infos_update.column_name = NEW.column_name;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Values are the same';
END IF;
END\\`
It prevents duplicates on initial insert, but when the row is already inserted, on duplicate key update can still update rows with duplicate old and new values; even when simply deleting the row before the update with that trigger

IF NOT EXISTS in mysql showing syntax error

I am trying to convert this tsql to mysql but showing error need help
CREATE PROCEDURE FormAdd
#formName varchar(MAX)
AS
IF NOT EXISTS(SELECT * FROM tbl_Form WHERE formName=#formName)
BEGIN
INSERT INTO tbl_Form
(formName)
VALUES
(#formName)
SELECT ##identity
END
ELSE
BEGIN
SELECT '-1'
END
mysql
CREATE PROCEDURE FormAdd
(p_formName varchar(500) )
begin
INSERT INTO tbl_Form (formName)
VALUES (p_formName)
where NOT EXISTS(SELECT * FROM tbl_Form WHERE formName=p_formName) ;
SELECT Last_insert_id() as returnvalue ;
SELECT '-1' ;
end
Your attempt was syntactically invalid because logically, an INSERT statement cannot contain a WHERE clause since it does not act on existing rows.
If the purpose is to insert only if the value for p_formname is not already present, then an appropriate step would be to define a unique index on that column first. Then, construct your procedure to attempt the insert and inspect the ROW_COUNT() value to see if one was inserted and act accordingly, returning -1 if not to adapt your existing T-SQL procedure.
First create the unique index on p_formname:
ALTER TABLE tbl_Form ADD UNIQUE KEY `idx_formName` (`formName`);
Then your procedure should use INSERT INTO...ON DUPLICATE KEY UPDATE to attempt to insert the row. Per the documentation, the value of ROW_COUNT() will be 0 if a new row was not inserted or 1 if it was.
CREATE PROCEDURE FormAdd (p_formName varchar(500))
BEGIN
/* Attempt the insert, overwrite with the same value if necessary */
INSERT INTO tbl_Form (formName) VALUES (p_formName) ON DUPLICATE KEY UPDATE formName = p_formName;
/* Return the LAST_INSERT_ID() for a new row and -1 otherwise */
SELECT
CASE
WHEN ROW_COUNT() = 1 THEN LAST_INSERT_ID()
ELSE -1
END AS returnValue;
END

Trigger to return duplicate error in MySQL and to affect the INSERT INTO..... ON DUPLICATE KEY UPDATE

I am having a challenge with MySQL. As you know MySQL does not consider NULL as a duplicate value in the unique indexes.
The problem here is that I have a table but I need to enforces that no similar data to be inserted "including NULL" into the following columns
(call_code_id, result_code_id, action_method, action_id, assign_to, assign_to_type)
You may suggest for me to add a unique index on those columns. The problem that the columns "assign_to" and "assign_id" allows NULL values. So based on MySQL definition of a unique columns the following 2 set of data are not Unique and will be allowed to be inserted
(call_code_id, result_code_id, action_method, action_id, assign_to, assign_to_type)
(1,1,'FINISH',NULL,5,2)
(1,1,'FINISH',NULL,5,2)
so even if I add a unique column, MySQL will allow those 2 rows to be inserted. But I need to prevent that for happening as my application consider those 2 rows duplicates.
I must say here that I update this table using stored procedure and I update the table using INSERT INTO ...... ON DUPLICATE KEY UPDATE statement.
My initial solution was to add a 2 triggers on that table. First trigger for insert and the other for update. Both trigger will return error code 1062 if a similar data was found before insert or update.
That did worked somewhat! But the new challenge is that the error code 1062 that is return by the trigger is not being utilized by the INSERT INTO ...... ON DUPLICATE KEY UPDATE statement. However, it is returning the 1062 error on the screen so it is finding the duplicates as it should. So when I execute the INSERT INTO ...... ON DUPLICATE KEY UPDATE statement I get the error 1062 returned on the screen, instead it should perform an update since I am asking it to update when duplicate records are found.
Here are my triggers syntax
DROP TRIGGER IF EXISTS `return_1062_on_duplicate_on_insert`;
DELIMITER //
CREATE TRIGGER `return_1062_on_duplicate_on_insert` BEFORE INSERT ON `inventory_engine_test`
FOR EACH ROW BEGIN
IF EXISTS(SELECT 1 FROM inventory_engine_test
WHERE call_code_id = NEW.call_code_id
AND result_code_id = NEW.result_code_id
AND action_method = NEW.action_method
AND IFNULL(action_id, 0) = IFNULL(NEW.action_id, 0)
AND IFNULL(assign_to, 0) = IFNULL(NEW.assign_to, 0)
AND assign_to_type = NEW.assign_to_type
AND NEW.engine_id IS NOT NULL) THEN
SIGNAL SQLSTATE '23000'
SET MYSQL_ERRNO = 1062,
TABLE_NAME = 'inventory_engine_test',
MESSAGE_TEXT = 'Duplicate key found - return_1062_on_duplicate_on_insert trigger';
END IF;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `return_1062_on_duplicate_on_update`;
DELIMITER //
CREATE TRIGGER `return_1062_on_duplicate_on_update` BEFORE UPDATE ON `inventory_engine_test`
FOR EACH ROW BEGIN
IF EXISTS(SELECT 1 FROM inventory_engine_test
WHERE call_code_id = NEW.call_code_id
AND result_code_id = NEW.result_code_id
AND action_method = NEW.action_method
AND IFNULL(action_id, 0) = IFNULL(NEW.action_id, 0)
AND IFNULL(assign_to, 0) = IFNULL(NEW.assign_to, 0)
AND assign_to_type = NEW.assign_to_type
AND NEW.engine_id IS NOT NULL) THEN
SIGNAL SQLSTATE '23000'
SET MYSQL_ERRNO = 1062,
TABLE_NAME = 'inventory_engine_test',
MESSAGE_TEXT = 'Duplicate key found - return_1062_on_duplicate_on_update trigger';
END IF;
END
//
DELIMITER ;
How can I get INSERT INTO ..... ON DUPLICATE KEY UPDATE to consider the error return by the trigger?
Thanks

Log exception info in MySQL stored procedure

As I know, I can define exception handler in MySQL stored procedure, but seems I can't catch the exception message in the handler and write a log in a table for debugging purpose. I just want to know is there method to log exception code and message in MySQL store procedure?
You can catch the message, the error code, the sql state, ..., with the GET DIAGNOSTICS statement, in 5.6.4
See
http://dev.mysql.com/doc/refman/5.6/en/get-diagnostics.html
I don't remember what tutorial I copied this from. However, it has helped immensely in the versions of MySQL prior to 5.6. Thanks to whomever I learned this from!
Step 1 : Create a debug_log table. This will hold everything your write to it.
CREATE TABLE `debug_log` (
`debug_log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`msg` varchar(512) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`debug_log_id`)
) ENGINE=MyISAM
Step 2 : Create a stored procedure for adding info to the debug_log table.
DELIMITER $$
USE `your_db_name_here`$$
DROP PROCEDURE IF EXISTS `log_debug`$$
CREATE DEFINER=`ss7admin`#`%` PROCEDURE `log_debug`(IN lastMsg VARCHAR(512))
BEGIN
INSERT INTO debug_log (msg) VALUES (lastMsg);
END$$
DELIMITER ;
Step 3 : Add a handler in your real stored procedure.
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
CALL log_debug(
CONCAT
(
now(),
' : Something went horribly wrong!',
'.'
)
);
CALL log_debug('Exiting now');
SET outReturnCode = 1;
END;
You cannot catch the message, but you can catch the error code.
Here is an example of how to deal with "Duplicate entry" (PK, UK constraint):
CREATE PROCEDURE sp_do_insert(
IN in_param1 int,
IN in_param2 int,
OUT out_status tinyint
)
BEGIN
DECLARE CONTINUE HANDLER FOR 1062 SET out_status = 1;
SET out_status = 0;
INSERT INTO tbl(field1, fiel2)
VALUES (in_param1, in_param2);
END;
If tbl has a UK constraint on field1 and you try to insert an existing value once again you will not get an error. Nothing will be inserted and status will be equal to 1.
You can also add other handlers for other error codes. And you will always know what is the error from out_status value and you will know "error message" from error_code (in handler).
You can try to play with show warnings (it shows errors/warnings for the last query) in case if out_status <> 0.
Hope it helps.