Is it possible to catch unique index violation and raise_application_error. We are trying with a blanket trigger and catching the exceptions, however we are always getting oracle exception.
ORA-00001: unique constraint (TEST_UNIQUE_INDEX) violated
We have a FUNCTION-BASED constraint on the table.
CREATE TABLE TEST_CONSTRAINT(
"ID" NUMBER NOT NULL ENABLE,
"LOCATION" VARCHAR2(20) NOT NULL,
"DEPT" VARCHAR2(20) NOT NULL,
"RECORD" NUMBER NOT NULL)
/
CREATE UNIQUE INDEX TEST_UNIQUE_INDEX ON TEST_CONSTRAINT (
CASE "RECORD" WHEN 1 THEN "LOCATION" ELSE NULL END,
CASE "RECORD" WHEN 1 THEN "DEPT" ELSE NULL END)
/
before update trigger code
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('SQLERRM '|| ERROR_MESSAGE);
WHEN OTHERS THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('SQLERRM '|| ERROR_MESSAGE);
END;
test schema here
Edit 1:
The requirement here is to impose selective uniqueness i.e. only one set of location/dept can be set as a record (being a boolean in app). In all other cases, we have to generate a raise_application_error.
The trigger would be executed BEFORE the update happens. Since there would be no exception during its execution (all it does is print the system date), no exception would be raised. Then, the update is actually executed, and that's when you get the error message from the database.
To catch the exception that occurs because of the UPDATE statement, the UPDATE statement itself must be enclosed within a PL/SQL block, as below.
DECLARE
V_DATE DATE;
ERROR_MESSAGE VARCHAR2(1000);
BEGIN
SELECT SYSDATE INTO V_DATE FROM DUAL;
DBMS_OUTPUT.PUT_LINE('SYSDATE '|| V_DATE);
UPDATE TEST_CONSTRAINT SET RECORD = 1 WHERE ID = 3;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('Error occurred. SQLERRM '|| ERROR_MESSAGE);
WHEN OTHERS THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('SQLERRM '|| ERROR_MESSAGE);
END;
/
Reference:
Handling PL/SQL Errors on Oracle® Database PL/SQL Language Reference
Related
I wrote trigger that contains the following lines:
DECLARE is_journal TINYINT(1);
SET #is_journal := 1;
IF (is_journal IS NOT NULL) THEN
INSERT INTO `log` VALUES("is_journal is not null");
ELSE
INSERT INTO `log` VALUES("is_journal is null");
END IF;
And in my log table I always get as result
is_journal is null
Why does this happen?
As you are setting value to #is_journal. And comparing value of is_journal.
So first of all you have knowledge that both this thing are different.
So instead of
IF (is_journal IS NOT NULL) THEN line put
IF (#is_journal IS NOT NULL) THEN .
my trigger is coming back with no errors. But when i test it, no error message is returned and the test data is stored in the table.. Which isn't what I want.
Basically (this is for coursework), I want a trigger that displays an error message when a location other than Barcelona is inserted into the table.
This is my trigger. As I said, no errors come back, but it doesn't work?
DELIMITER $$
CREATE TRIGGER after_location_insert_finance
AFTER INSERT ON Finance
FOR EACH ROW
BEGIN
DECLARE LocationTrigger varchar(255);
DECLARE msg varchar(255);
SELECT Location INTO LocationTrigger
FROM Finance
WHERE Location != "Barcelona";
IF(LocationTrigger != "Barcelona") THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Not a valid location';
END IF;
END$$
The expression in the IF won't evaluate to TRUE if LocationTrigger is null.
And LocationTrigger will be NULL if there are not rows in Boss that satisfy the condition in the WHERE clause.
I'm not understanding why there's no check of the value being inserted into the location column; or why are we running a query of the Boss table, when we're comparing to literal value of 'Barcelona'.
Seems like we'd want this kind of check in a BEFORE trigger rather than an AFTER trigger.
Are we going to allow NULL values for location?
If the only value we allow to be inserted for location is 'Barcelona', and if we aren't allowing NULL values, then we don't need to mess with any query of the Boss table. Just do a comparison...
BEGIN
IF NOT ( NEW.location <=> 'Barcelona' ) THEN
SIGNAL ...
END IF;
END
Maybe the goal of the trigger is to enforce a referential integrity constraint (which we'd normally implement by declaring a FOREIGN KEY constraint, rather than implementing a procedure.)
Assuming that a "valid" location is defined by the domain of location values in Boss table,
and assuming we aren't going to allow NULL values, then:
BEGIN
DECLARE ls_location VARCHAR(255) DEFAULT NULL;
-- check if the value being inserted exists in a lookup table
SELECT b.location INTO ls_location
FROM Boss b
WHERE b.location = NEW.location
LIMIT 1
;
-- if we didn't find a match, ls_location will be NULL
IF ls_location IS NULL THEN
SIGNAL ...
END IF;
END
If no matching row is found in Boss, then ls_location will be NULL.
A "not equals" comparison to NULL is going to evaluate to NULL, not TRUE or FALSE.
Boolean logic is SQL is tri-valued... TRUE, FALSE and NULL
SELECT 0 = 1 AS c0
, 0 <> 1 AS c1
, 0 = NULL AS c2
, 0 <> NULL AS c3
, NULL = NULL AS c4
, NULL <> NULL AS c5
MySql dont supported CHECK Clause so i think i must be to use TRIGGER on a Table
in a simple Table we have two field that opinion field must be in ('normal','bad','good') :
CREATE TABLE `user`.`opinionTable` (
`uid` INT NOT NULL,
`opinion` VARCHAR(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`uid`,`opinion`),
CHECK (opinion IN ('normal','bad','good'))
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
i want to check opinion data is a allowed value ('normal','bad','good') or no and when is not allowed i want to Transaction to Rollbacked
i tried this TRIGGER but not worked so what is the correct TRIGGER statement ?
CREATE TRIGGER check_values BEFORE INSERT ON `opinionTable`
FOR EACH ROW
BEGIN
IF (NEW.opinion IN ('normal','bad','good'))
THEN
END IF;
END
i catch this exception :
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'END IF;
mysql version is 5.1.34 community
jdbc version is 5.1.23
There are two ways to implement check constraint, if the inputs are not matched.
Force to use default values.
SIGNAL an error and abort the transaction.
Example 1: Force to use default values
You can define to use default value to silently use in case an invalid input is encountered.
IF ( LOWER( NEW.opinion ) NOT IN ('normal','bad','good')) THEN
SET default_opinion := 'normal'; -- declare this field first
NEW.opinion := default_opinion; -- change its value as desired
ELSE
NEW.opinion := LOWER( NEW.opinion ); -- change this as desired
END IF;
Example 2: SIGNAL an error and abort the transaction
Define an error state number for the case and relevant error messages.
Use the same to SIGNAL the error.
IF ( LOWER( NEW.opinion ) NOT IN ('normal','bad','good')) THEN
-- don't forget to declare variables first and then use
SET error_message := CONCAT( 'Invalid opinion option: ', NEW.opinion );
-- set proper error state number -- 302478 is just an example
SIGNAL SQLSTATE '302478' SET MESSAGE_TEXT := error_message;
END IF;
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 ;
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.