CREATE OR REPLACE trigger million_trigger
BEFORE INSERT or UPDATE on employee
FOR EACH ROW
WHEN (new.SALARY>1000000)
DECLARE
txt EXCEPTION;
BEGIN
if INSERTING or UPDATING then
RAISE txt ;
end if;
EXCEPTION
WHEN txt THEN
DBMS_OUTPUT.PUT_LINE('SALARY TOO HIGH');
end;
/
Hello, I created a trigger which is checks if the salary from table employee is greater than 1,000,000. If the salary is greater, then the trigger is supposed to stop the execution of an insert statement from a stored procedure. The trigger has been successfully created but when I insert a record with salary > 1,000,000 nothing happens. ( the record gets inserted - which is not supposed to happen ) Any idea?
You are catching the exception so the trigger doesn't throw an error. Since the trigger doesn't throw an error, the INSERT statement continues and, ultimately, succeeds. If you happen to have enabled serveroutput in your session, you would see the "Salary too high" message but you should never depend on data written to the dbms_output buffer being read by anyone.
If you want to stop the execution of the INSERT statement, you would need to remove your exception handler. Most likely, you would also want to change how you are raising the exception
IF( :new.salary > 1000000 )
THEN
RAISE_APPLICATION_ERROR( -20001, 'Salary too high' );
END IF;
Related
I have the following table that describes which chemical elements each planet is composed of using percentage.
CREATE TABLE elem_in_planet
(
id_planet INTEGER,
element_symbol CHAR(2),
percent_representation NUMBER CONSTRAINT NN_elem_in_planet NOT NULL,
CONSTRAINT PK_elem_in_planet PRIMARY KEY (id_planet, element_symbol),
CONSTRAINT FK_planet_has_elem FOREIGN KEY (id_planet) REFERENCES planet (id_planet),
CONSTRAINT FK_elem_in_planet FOREIGN KEY (element_symbol) REFERENCES chemical_element (element_symbol)
);
I'm trying to make a trigger that warns users when they add a new element to a planet and the sum of elements in that planet exceeds 100%. I came up with this.
CREATE OR REPLACE TRIGGER elem_in_planet_check
AFTER INSERT OR UPDATE ON elem_in_planet
FOR EACH ROW
DECLARE
sum_var NUMBER;
PRAGMA autonomous_transaction;
BEGIN
SELECT SUM(percent_representation)
INTO sum_var
FROM elem_in_planet
WHERE id_planet = :NEW.id_planet
GROUP BY id_planet;
EXCEPTION
WHEN NO_DATA_FOUND THEN
sum_var := 0;
IF sum_var > 100 THEN
DBMS_OUTPUT.put_line('WARNING: Blah blah.');
END IF;
END;
/
This code seems to throw the NO_DATA_FOUND exception every single time, even though I have inserted test data and when I run the SQL query alone, it works as expected.
I'm new to this and don't understand what I'm doing wrong.
Thank you for any advice.
You have NOT inserted the row into the table, 2 reasons.
The trigger runs as part of the insert statement which has not
completed. So the row does not exist.
You specified "PRAGMA autonomous_transaction" (AKA the create
untraceable bug here statement), did you previously get a mutating
table exception. So you cannot see any data inserted/updated/deleted
by the current transaction.Further if an error did occur the row would still be inserted as you did not raise an error or re-raise the existing error. I suggest you familiarize yourself with the PLSQL block structure. For now you may want to try:
You could use an after statement trigger or after statement section of compound trigger to make this test, do raise_application_error if sum > 100;
BTW as it stands your "if on sum_var > 100" runs only when an error occurs. Anything after the "EXCEPTION" and before END for that block runs only when a error occurs.
create or replace trigger elem_in_planet_check
after insert or update on elem_in_planet
declare
error_detected boolean := False;
begin
for planet in
(
select id_planet, sum_var
from (select id_planet, sum(percent_representation) sum_var
from elem_in_planet
group by id_planet
)
where sum_var > 100
)
loop
dbms_output.put_line('Planet ' || planet.id_planet || ' at '|| planet.sum_var || '% resources exceed 100%');
error_detected:= True;
end loop;
if error_detected then
Raise_application_error('-20001', 'Planet resources cannot exceed 100%');
end if;
end elem_in_planet_check;
I create the trigger A1 so that an article with a certain type, that is 'Bert' cannot be added more than once and it can have only 1 in the stock.
However, although i create the trigger, i can still add an article with the type 'Bert'. Somehow, the count returns '0' but when i run the same sql statement, it returns the correct number. It also starts counting properly if I drop the trigger and re-add it. Any ideas what might be going wrong?
TRIGGER A1 BEFORE INSERT ON mytable
FOR EACH ROW
DECLARE
l_count NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT COUNT(*) INTO l_count FROM mytable WHERE article = :new.article;
dbms_output.put_line('Count: ' || l_count);
IF l_count >0 THEN
IF(:new.TYPEB = 'Bert') THEN
dbms_output.put_line('article already exists!');
ROLLBACK;
END IF;
ELSIF (:new.TYPEB = 'Bert' AND :new.stock_count>1) THEN
dbms_output.put_line('stock cannot have more than 1 of this article with type Bert');
ROLLBACK;
END IF;
END;
This is the insert statement I use:
INSERT INTO mytable VALUES('Chip',1,9,1,'Bert');
A couple of points. First, you are misusing the autonomous transaction pragma. It is meant for separate transactions you need to commit or rollback independently of the main transaction. You are using it to rollback the main transaction -- and you never commit if there is no error.
And those "unforeseen consequences" someone mentioned? One of them is that your count always returns 0. So remove the pragma both because it is being misused and so the count will return a proper value.
Another thing is don't have commits or rollbacks within triggers. Raise an error and let the controlling code do what it needs to do. I know the rollbacks were because of the pragma. Just don't forget to remove them when you remove the pragma.
The following trigger works for me:
CREATE OR REPLACE TRIGGER trg_mytable_biu
BEFORE INSERT OR UPDATE ON mytable
FOR EACH ROW
WHEN (NEW.TYPEB = 'Bert') -- Don't even execute unless this is Bert
DECLARE
L_COUNT NUMBER;
BEGIN
SELECT COUNT(*) INTO L_COUNT
FROM MYTABLE
WHERE ARTICLE = :NEW.ARTICLE
AND TYPEB = :NEW.TYPEB;
IF L_COUNT > 0 THEN
RAISE_APPLICATION_ERROR( -20001, 'Bert already exists!' );
ELSIF :NEW.STOCK_COUNT > 1 THEN
RAISE_APPLICATION_ERROR( -20001, 'Can''t insert more than one Bert!' );
END IF;
END;
However, it's not a good idea for a trigger on a table to separately access that table. Usually the system won't even allow it -- this trigger won't execute at all if changed to "after". If it is allowed to execute, one can never be sure of the results obtained -- as you already found out. Actually, I'm a little surprised the trigger above works. I would feel uneasy using it in a real database.
The best option when a trigger must access the target table is to hide the table behind a view and write an "instead of" trigger on the view. That trigger can access the table all it wants.
You need to do an AFTER trigger, not a BEFORE trigger. Doing a count(*) "BEFORE" the insert occurs results in zero rows because the data hasn't been inserted yet.
Hello fellow programmers and happy new year to you all!
I have few university tasks for winter break and one of them is to create trigger on table:
PERSON(ID, Name, Surname, Age);
Trigger is supposed to inform user when they have inserted row with invalid ID. Vadility criteria is that ID is 11 digits long.
I tried to write solution like this:
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
DECLARE
idNew VARCHAR(50);
lengthException EXCEPTION;
BEGIN
SELECT id INTO idNew FROM INSERTED;
IF LENGTH(idNew) <> 11 THEN
RAISE lengthException;
END IF;
EXCEPTION
WHEN lengthException THEN
dbms_output.put_line('ID for new person is INVALID. It must be 11 digits long!');
END;
Then I realized that INSERTED exists only in sqlserver and not in oracle.
What would you suggest I could do to fix that?
Thanks in advance!
Do you want to raise an exception (which would prevent the insert from succeeding)? Or do you want to allow the insert to succeed and write a string to the dbms_output buffer that may or may not exist and may or may not be shown to a human running the insert?
In either case, you'll want this to be a row-level trigger, not a statement-level trigger, so you'll need to add the for each row clause.
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
FOR EACH ROW
If you want to raise an exception
BEGIN
IF( length( :new.id ) <> 11 )
THEN
RAISE_APPLICATION_ERROR( -20001,
'The new ID value must have a length of 11' );
END IF;
END;
If you want to potentially print output but allow the insert to succeed
BEGIN
IF( length( :new.id ) <> 11 )
THEN
dbms_output.put_line( 'The new ID value must have a length of 11' );
END IF;
END;
Of course, in reality, you would never use a trigger for this sort of thing. In the real world, you would use a constraint.
I have a table Movie.
create table Movie
(
mv_id number(5),
mv_name varchar2(30),
startdate date
)
insert into Movie values(1,'AVATAR','8-MAR-2012');
insert into Movie values(2,'MI3','20-MAR-2012');
insert into Movie values(3,'BAD BOYS','10-Feb-2012');
I want to create a trigger which will ensure that no records is deleted if satrt date is earlier than current date.
My trigger code is --
create or replace trigger trg_1
before delete
on Movie
for each row
when(old.startdate < sysdate)
begin
raise_application_error(-20001, 'Records can not be deleted');
end;
The trigger is created.When I execute this code to delete data --
delete from Movie where mv_id=1;
Then the trigger fires but with errors, I don't know why I am getting such error.
I dont want any error, i want only message.
This is the error --
delete from Movie where mv_id=1
*
ERROR at line 1:
ORA-20010: Records can not be deleted
ORA-06512: at "OPS$0924769.TRG_1", line 3
ORA-04088: error during execution of trigger 'OPS$0924769.TRG_1'
I want to get rid of this error.
The only way for a trigger on a table to prevent any operation is throw an exception. You cannot have a trigger that prevents a DELETE and prints a message.
You can write a trigger that attempts to display a message and that allows the delete to happen
create or replace trigger trg_1
before delete
on Movie
for each row
when(old.startdate < sysdate)
begin
dbms_output.put_line( 'Records can not be deleted');
end;
If the client happens to be configured to display the data written to the DBMS_OUTPUT buffer (most clients will not), this will display a message. But it will also allow the delete to be successful which does not sound like what you want.
Add exception handling in your delete statement.
All you need to do is write the delete statement in its own BEGIN and END block, and handle the exception there which is thrown from trigger. No row would be deleted if it matches the condition; however, the message to deny record deletion would be displayed using DBMS_OUTPUT command instead.
The below code does what you want:
BEGIN
DELETE FROM MOVIE WHERE MV_ID = 1;
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE('Records can not be deleted.');
END;
You can check the message in Output window. Below is the output I got in PL/SQL developer after executing above block of query.
Records can not be deleted.
I hope it helps.
I'm working on migration of data from a legacy system into our new app(running on Oracle Database, 10gR2). As part of the migration, I'm working on a script which inserts the data into tables that are used by the app.
The number of rows of data that are imported runs into thousands, and the source data is not clean (unexpected nulls in NOT NULL columns, etc). So while inserting data through the scripts, whenever such an exception occurs, the script ends abruptly, and the whole transaction is rolled back.
Is there a way, by which I can continue inserts of data for which the rows are clean?
Using NVL() or COALESCE() is not an option, as I'd like to log the rows causing the errors so that the data can be corrected for the next pass.
EDIT: My current procedure has an exception handler, I am logging the first row which causes the error. Would it be possible for inserts to continue without termination, because right now on the first handled exception, the procedure terminates execution.
If the data volumes were higher, row-by-row processing in PL/SQL would probably be too slow.
In those circumstances, you can use DML error logging, described here
CREATE TABLE raises (emp_id NUMBER, sal NUMBER
CONSTRAINT check_sal CHECK(sal > 8000));
EXECUTE DBMS_ERRLOG.CREATE_ERROR_LOG('raises', 'errlog');
INSERT INTO raises
SELECT employee_id, salary*1.1 FROM employees
WHERE commission_pct > .2
LOG ERRORS INTO errlog ('my_bad') REJECT LIMIT 10;
SELECT ORA_ERR_MESG$, ORA_ERR_TAG$, emp_id, sal FROM errlog;
ORA_ERR_MESG$ ORA_ERR_TAG$ EMP_ID SAL
--------------------------- -------------------- ------ -------
ORA-02290: check constraint my_bad 161 7700
(HR.SYS_C004266) violated
Using PLSQL you can perform each insert in its own transaction (COMMIT after each) and log or ignore errors with an exception handler that keeps going.
Try this:
for r_row in c_legacy_data loop
begin
insert into some_table(a, b, c, ...)
values (r_row.a, r_row.b, r_row.c, ...);
exception
when others then
null; /* or some extra logging */
end;
end loop;
DECLARE
cursor;
BEGIN
loop for each row in cursor
BEGIN -- subBlock begins
SAVEPOINT startTransaction; -- mark a savepoint
-- do whatever you have do here
COMMIT;
EXCEPTION
ROLLBACK TO startTransaction; -- undo changes
END; -- subBlock ends
end loop;
END;
If you use sqlldr you can specify to continue loading data, and all the 'bad' data will be skipped and logged in a separate file.