ORACLE Mutating trigger error [duplicate] - oracle

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
ORACLE After update trigger: solving ORA-04091 mutating table error
So I have a trigger to check if a an admin has been locked out of login (if they are they will have a 1 set to temp_pw. It then send the admin a four digit pass code to unlock their account. Problem is I update the failed_logins field, incrementing it by 1 for every failed login before the trigger is called.
The rest of the trigger checks if there is an admin has a locked account before sending them an email with a pass code.
If I take out the Update Pi_admin_table set blah blah it sends the email but if I include it to insert the new pass code in the table it errors with this:
Message: 60 ORA-00060: deadlock detected while waiting for resource
ORA-06512: at "PI_USER_ADMIN.TR_ADMIN_LOCKOUT", line 17
ORA-04088: error during execution of trigger
'PI_USER_ADMIN.TR_ADMIN_LOCKOUT' UPDATE *pi_admin_table SET
failed_logins = failed_logins + 1 where
EMAIL='nathan#perceptive.co.uk' returning failed_logins into :bind_var
Here's my trigger:
create or replace
TRIGGER "TR_ADMIN_LOCKOUT"
AFTER UPDATE ON PI_ADMIN_TABLE
for each row
declare
-- pragma autonomous_transaction seems to fix trigger mutation errors.
-- Look at rewriting trigger later.
--pragma autonomous_transaction;
tempEmail varchar2(80 BYTE);
tempID varchar2(80 BYTE);
mail_host varchar2(255);
mail_port varchar2(255);
mail_from varchar2(255);
tempPW int;
begin
SELECT EMAIL, ADMINID
into tempEmail, tempID
from pi_admin_table
where TEMP_PW = :NEW.TEMP_PW;
SELECT MAIL_HOST, MAIL_PORT, MAIL_FROM
into mail_host, mail_port, mail_from
from pi_settings_table;
select dbms_random.value(1,10000)
into tempPW
from dual;
if tempEmail IS NOT NULL then
UPDATE PI_ADMIN_TABLE SET RESET_PW=round(tempPW) where adminid=tempID;
send_mail(tempEmail,
mail_host,
mail_port,
mail_from,
'Locked Out Event',
'Your administrator account was locked out. '|| chr(10) || chr(10) ||
'Please use this four digit pass code next time try to log in' ||
chr(10) || chr(10) ||
'Temp pass code: '|| round(tempPW) );
end if;
END;

Oracle does not allow code in a ROW trigger to issue a SELECT, INSERT, UPDATE, or DELETE against the table on which the trigger is defined. Your choices are to use an AUTONOMOUS TRANSACTION (but see the warning at the post referenced in #Ben's comment above) or use a COMPOUND TRIGGER.
Share and enjoy.

I would recommend you not to use trigger for that task. Encapsulate the logic you're trying to achieve in stored procedure.

Related

Making a trigger with RAISERROR [duplicate]

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.

How to delete sequences and procedures during logoff trigger?

Could you please help me in a unique situation I am in. I am receiving "ORA-30511: invalid DDL operation in system triggers" when dropping sequences and procedures during logoff trigger.
I need to delete tables, sequences and procedures of users before logoff event happens. I am writing the table details in DB_OBJECTS table upon create using a separate trigger. Below is my logoff trigger - could you please help me where I am doing wrong. Dropping tables is working fine in the below code. Only Dropping sequences and procedures is giving me "ORA-30511: invalid DDL operation in system triggers" error.
CREATE OR REPLACE TRIGGER DELETE_BEFORE_LOGOFF
BEFORE LOGOFF ON DATABASE
DECLARE
USER_ID NUMBER := SYS_CONTEXT('USERENV', 'SESSIONID');
BEGIN
FOR O IN (SELECT USER, OBJECT_NAME, OBJECT_TYPE
FROM DB_OBJECTS WHERE SID = USER_ID
AND USERNAME = USER AND SYSDATE > CREATED_DTTM) LOOP
IF O.OBJECT_TYPE = 'TABLE' THEN
EXECUTE IMMEDIATE 'DROP TABLE ' || O.USER || '.' || O.OBJECT_NAME || ' CASCADE CONSTRAINTS';
ELSIF O.OBJECT_TYPE = 'SEQUENCE' THEN
EXECUTE IMMEDIATE 'DROP SEQUENCE ' || O.USER || '.' || O.OBJECT_NAME;
ELSIF O.OBJECT_TYPE = 'PROCEDURE' THEN
EXECUTE IMMEDIATE 'DROP PROCEDURE ' || O.USER || '.' || O.OBJECT_NAME;
END IF;
END LOOP;
EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
END;
/
That's a simple one.
Error code: ORA-30511
Description: invalid DDL operation in system triggers
Cause: An attempt was made to perform an invalid DDL operation in a system trigger. Most DDL operations currently are not supported in system triggers. The only currently supported DDL operations are table operations and ALTER/COMPILE operations.
Action: Remove invalid DDL operations in system triggers.
That's why only
Dropping tables is working fine
succeeded.
Therefore, you can't do that using trigger.
You asked (in a comment) how to drop these objects, then. Manually, as far as I can tell. Though, that's quite unusual - what if someone accidentally logs off? You'd drop everything they created. If you use that schema for educational purposes (for example, every student gets their own schema), then you could create a "clean-up" script you'd run once class is over. Something like this:
SET SERVEROUTPUT ON;
DECLARE
l_user VARCHAR2 (30) := 'SCOTT';
l_str VARCHAR2 (200);
BEGIN
IF USER = l_user
THEN
FOR cur_r IN (SELECT object_name, object_type
FROM user_objects
WHERE object_name NOT IN ('EMP',
'DEPT',
'BONUS',
'SALGRADE'))
LOOP
BEGIN
l_str :=
'drop '
|| cur_r.object_type
|| ' "'
|| cur_r.object_name
|| '"';
DBMS_OUTPUT.put_line (l_str);
EXECUTE IMMEDIATE l_str;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
END LOOP;
END IF;
END;
/
PURGE RECYCLEBIN;
It is far from being perfect; I use it to clean up my Scott schema I use to answer questions on various sites so - once it becomes a mess, I run that PL/SQL code several times (because of possible foreign key constraint).
Other option is to keep a create user script(s) (along with all grant statements) and - once class is over - drop existing user and simply recreate it.
Or, if that user contains some pre-built tables, keep export file (I mean, result of data pump export) and import it after the user is dropped.
There are various options - I don't know whether I managed to guess correctly, but now you have something to think about.

Oracle DBMS_ALERT in Oracle 12c

I have a table (my_tab) that contains a STATUS column against a specific ID in this same table.
I need a means of being alerted via a DBMS_ALERT process of when the STATUS column changes value.
I was looking at using a trigger to kick off the ALERT, i.e.:
create or replace trigger my_tab_upd after update of status on my_tab for each row
begin
dbms_alert.signal('mystatusalert', 'changed from '||:old.status||' to '||:new.status||'.');
end;
/
With this, how do I now get alerted/notified that this STATUS change has occurred within a PL/SQL procedure to now go off and perform another operation based on this STATUS change?
Further to the above, with my application setup, there will be multiple users. Based on this, how can I target the alert for specific users/sessions so that the correct user gets their alert only and not someone else's.
I am looking at checking the alert from a web based application (Oracle APEX), so don't want to lock the front-end up so any recommendations on this would be good.
An example would be great.
I'd send an e-mail to myself. For example:
create or replace trigger my_tab_upd
after update of status on my_tab
for each row
begin
utl_mail.send (sender => 'me#company.com',
recipients => 'me#company.com',
subject => 'MY_TAB status changed',
message => 'old = ' || :old.status ||', new = ' || :new.status
);
end;
DBMS_ALERT example: in Scott's schema, I want to notify my stored procedure that something has changed in the EMP table and then do something (I'll just display the message).
First, create a triggger; alert name is alert_emp and will be used later in the stored procedure:
SQL> create or replace trigger trg_au_emp
2 after update on emp
3 for each row
4 begin
5 dbms_alert.signal
6 ('alert_emp', 'Salary changed for ' || :new.ename ||
7 ' from ' || :old.sal ||
8 ' to ' || :new.sal);
9 end;
10 /
Trigger created.
The procedure:
SQL> create or replace procedure p_test is
2 l_msg varchar2(200);
3 l_status number;
4 begin
5 dbms_alert.register ('alert_emp');
6 dbms_alert.waitone ('alert_emp', l_msg, l_status);
7 dbms_output.put_line(l_msg ||': '|| l_status);
8 end;
9 /
Procedure created.
Now, execute the procedure:
SQL> exec p_test;
Here, it is just waiting for something to happen in the EMP table. In another session I'm updating the table. Commit is obligatory; otherwise, nothing happens. p_test will still be waiting.
update emp set sal = 1000 where empno = 7369;
commit;
In the first session, once commit is being executed, screen shows this:
PL/SQL procedure successfully completed.
Salary changed for SMITH from 800 to 1000: 0
PL/SQL procedure successfully completed.
SQL>

oracle trigger for user restriction based on terminal

I want to restrict Oracle users from logging into database except for a couple of terminal. I have written below trigger.
CREATE OR REPLACE TRIGGER TRG_IP_RESTRICT
AFTER LOGON ON DATABASE
DECLARE
V_USER VARCHAR2(30);
V_GRP VARCHAR2(50);
BEGIN
SELECT USER INTO V_USER FROM DUAL;
V_GRP := SYS_CONTEXT('USERENV', 'TERMINAL');
IF V_USER IN ('<list of users>') THEN
IF V_GRP NOT IN ('<list of terminals>') THEN
RAISE_APPLICATION_ERROR(-20001,
'Access Denied by DBA TEAM : ' || V_GRP ||
' on ' || V_USER || ' from ' ||
SYS_CONTEXT('USERENV', 'IP_ADDRESS'));
END IF;
END IF;
END;
Even though it's working fine and going in exception part, which should happen technically. But at the same time, it is allowing the connection instead of showing the error message to the user.
Can someone please help?
Such trigger works only for non-DBA users, none precisely users which do not have adminster database trigger privilege.
Otherwise you may block your entire database.
According to the PL/SQL Language Reference:
If the system trigger is a DATABASE LOGON trigger and the user has
ADMINISTER DATABASE TRIGGER privilege, then the user is able to log on
successfully even if the trigger raises an exception. For SCHEMA LOGON
triggers, if the user logging on is the trigger owner or has ALTER ANY
TRIGGER privileges then logon is permitted. Only the trigger action is
rolled back and an error is logged in the trace files and alert log.
You can workaround this restriction by raising an ORA-600 error that will break the entire session. The error message won't be helpful to the user but it at least stops them.
The sample code below will stop absolutely everyone from connecting to the database, even SYSDBA. Be very careful running this. Make sure you have another session connected to the database and run drop trigger TRG_IP_RESTRICT; when you are done testing it.
CREATE OR REPLACE TRIGGER TRG_IP_RESTRICT
AFTER LOGON ON DATABASE
DECLARE
V_USER VARCHAR2(30);
V_GRP VARCHAR2(50);
--Only an ORA-600 error can stop logons for users with either
--"ADMINISTER DATABASE TRIGGER" or "ALTER ANY TRIGGER".
--The ORA-600 also generates an alert log entry and may warn an admin.
internal_exception exception;
pragma exception_init( internal_exception, -600 );
BEGIN
SELECT USER INTO V_USER FROM DUAL;
V_GRP := SYS_CONTEXT('USERENV', 'TERMINAL');
IF V_USER IN ('<list of users>') THEN
IF V_GRP NOT IN ('<list of terminals>') THEN
raise internal_exception;
-- RAISE_APPLICATION_ERROR(-20001,
-- 'Access Denied by DBA TEAM : ' || V_GRP ||
-- ' on ' || V_USER || ' from ' ||
-- SYS_CONTEXT('USERENV', 'IP_ADDRESS'));
END IF;
END IF;
END;
/
With that trigger in place, even DBA users will get this error message when they connect:
ERROR:
ORA-00600: internal error code, arguments: [600], [], [], [], [], [], [], [],
[], [], [], []
ORA-06512: at line 21

PL/SQL trigger doesn't work properly

my sql trigger throws an error when added login exists in system but if not, no row is inserted.
Can somebody tell, why?
create or replace trigger user_login_exist_validator
before insert on USERS
for each row
declare
login varchar2(32 char) := :new.USER_LOGIN;
login_exists number(1,0) := 0;
begin
select 1 into login_exists from USERS where USER_LOGIN=login;
if login_exists > 0
then
RAISE_APPLICATION_ERROR(-20666, 'Użytkownik ' || login || ' już istnieje w systemie!!!');
end if;
end;
Looks like a mutating table error.
You are trying to read the same database table while you're already in the act of modifying data in it.
It might be better to take away the trigger. You can catch the exception and handle it where you do the insert statement.
https://docs.oracle.com/cd/B13789_01/appdev.101/b10807/07_errs.htm

Resources