Setting a column on a new row with a trigger - oracle

I have a table name dblog where the schema is like
data_balance_id number(8) primary key,
plan_id number(6) not null,
start_date date default current_date,
end_date date not null);
So I am trying to create a trigger which will update enddate column when a insertion is going to happen. enddate will be updated as 30 days from the insertion day. My trigger code is
CREATE OR REPLACE TRIGGER trg
BEFORE INSERT
ON dblog FOR EACH ROW
BEGIN
INSERT INTO dblog (end_date) values (SYSDATE()+30);
END;
/
The insert query is like following
insert into dblog (db_id, planid) values (12,123);
Trigger is created without any error. But at the time of insertion I am getting the following error
insert into dblog (db_id, planid) values (12,123)
*
ERROR at line 1:
ORA-00036: maximum number of recursive SQL levels (50) exceeded
ORA-06512: at "E1038351.TRG1", line 2

You just want to modify the :new pseudo-record. Something like this
CREATE OR REPLACE TRIGGER trg
BEFORE INSERT ON dblog
FOR EACH ROW
BEGIN
:new.end_date := sysdate + 30;
END;
If you don't want end_date to have a time component (or, rather, you want the time component to be midnight), you would want to trunc(sysdate) + 30.

You are trying to insert another row, which also re-triggers the trigger.
In the triggers you have access to the row using variables :NEW and :OLD.
In case when you insert, the :OLD is null , because you doesn't have it in the table yet.
So before inserting row you update his columns like this:
:NEW.END_DATE = SYSDATE+30;

Related

Oracle - update same table on which trigger is fired

I have a table temp_table with the following columns
Id number,
name varchar,
Password varchar,
pwd_change_date timestamp
I want to capture the timestamp in pwd_change_date column only when password column is changed.
So basically i want to use update statement inside the trigger to update timestamp value in pwd_change_date column for the same record.
Example
When a password is changed for one user, I want to capture the timestamp value in pwd_change_date for the same record.
I tried with before insert and after insert of password on temp_table, but getting mutation error. Is it allowed in Oracle to update the Same row/table on which trigger is fired?
You don't need to update the table again; you can modify the data before it is inserted, with a before-insert row level trigger, e.g.:
create trigger trig_pwd_date
before insert or update on temp_table
for each row
when (old.password is null and new.password is not null or new.password != old.password)
begin
:new.pwd_change_date := systimestamp;
end;
/
db<>fiddle demo
This used the new and old correlation names to decide if the password value has changed; and the new correlation name to assign the system time to the field in the pseudorecord, which becomes the column value when the insert completes.
Hopefully you aren't storing plain-text passwords in your table.
SQL> create table temp_table (password varchar2(50), pwd_change_date TIMESTAMP);
Table created.
SQL> create trigger trig_pwd_date
before insert or update on temp_table
for each row
when (old.password is null and new.password is not null or new.password != old.password)
begin
:new.pwd_change_date := systimestamp;
end; 2 3 4 5 6 7
8 /
Trigger created.
SQL> set time on
15:28:42 SQL> insert into temp_table values ('23456',sysdate);
1 row created.
15:29:01 SQL> commit;
Commit complete.
15:29:09 SQL> select * from temp_table;
PASSWORD
PWD_CHANGE_DATE
12345
21-SEP-20 03.28.02.370377 PM
23456
21-SEP-20 03.29.01.478017 PM

Trigger is not working when I'm trying to insert a value into a table

I am trying to make an Insert Trigger for a table called Marks which has id, id_student, id_course, value, data_notation, created_at, updated_at.
I need to make an Update on the old value, if the value I want to insert is higher than the one already exists in the column, and if there are no values in the column you would do an Insert with the new value.
I created the Trigger and there are no compilation errors.
CREATE OR REPLACE TRIGGER insert_value
before INSERT ON Marks
FOR EACH ROW
BEGIN
IF (:OLD.value IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('Inserting.. because value is null');
UPDATE Marks SET value = :NEW.value where id_student = :NEW.id_student;
ELSE
DBMS_OUTPUT.PUT_LINE('Updating old value.. if old value is smaller than the one we want');
IF (:OLD.value < :NEW.value) THEN
UPDATE Marks SET value = :NEW.value where :OLD.id_student = :NEW.id_student;
END IF;
END IF;
END;
I want to change the old value from an existing value 5 to null for a specific id.
update Marks set value = null where id = 692;
select * from Marks where id = 692;
But when I'm trying to insert a value into the table so I can change the value null into 6 via the trigger
INSERT INTO Marks
VALUES (692, 43, 12, 6, '13-02-2018', '13-02-2018', '13-02-2018');
I am receiving an error.
Error report -
SQL Error: ORA-00001: unique constraint (STUDENT.SYS_C007784) violated
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
And it prints one time:
Inserting.. because value is null
But when I'm trying to check if the trigger did its job, using:
SELECT * from Marks where id = 692;
It doesn't update anything.
It has to be a trigger triggered by an insert operation. So I can't make the insert into the table, but how else should I write it so it works?
You problem comes from recursive calling the trigger due to the insert. The following would work. It does not catch update statements. It only cares for inserts. If the row exists already the row gets deleted first and the existing value is used for the insert if the existing value is higher.
set lin 20000
drop table marks;
create table Marks(
id number,
id_student number,
id_course number,
value number,
data_notation varchar2(40),
created_at timestamp,
updated_at timestamp,
CONSTRAINT marks#u UNIQUE (id, id_student, id_course)
);
create or replace trigger mark_trigger
before insert on marks
for each row
declare
l_value number;
l_data_notation varchar2(40);
l_created_at timestamp;
begin
select value, data_notation, created_at
into l_value, l_data_notation, l_created_at
from
(select *
from marks
where marks.id = :new.id
and marks.id_student = :new.id_student
and marks.id_course = :new.id_course
order by created_at desc)
where rownum=1;
if l_value is null then
return;
end if;
if l_value > :new.value then
:new.value := l_value;
:new.data_notation := l_data_notation;
:new.created_at := l_created_at;
else
:new.updated_at := systimestamp;
end if;
delete from marks
where marks.id = :new.id
and id_student = :new.id_student
and id_course = :new.id_course;
exception
when no_data_found then
null;
end;
create or replace procedure marks_insert(
i_id number,
i_id_student number,
i_id_course number,
i_value number,
i_data_notation varchar2
)
is
begin
INSERT INTO marks
VALUES (i_id, i_id_student, i_id_course, i_value, i_data_notation, systimestamp, null);
END marks_insert;
begin
delete from marks;
marks_insert(1,1,1,5,'1 first entry');
marks_insert(1,1,1,6,'1 second entry');
marks_insert(1,1,2,3,'2 first entry');
marks_insert(1,1,2,2,'2 second entry');
end;
select * from marks;
Output:
Table dropped.
Table created.
Trigger created.
Procedure created.
PL/SQL procedure successfully completed.
ID ID_STUDENT ID_COURSE VALUE DATA_NOTATION CREATED_AT UPDATED_AT
---------- ---------- ---------- ---------- ---------------------------------------- -------------------------------------------------- --------------------------------------------------
1 1 1 6 1 second entry 07/05/2019 13:31:31.266817 07/05/2019 13:31:31.266928
1 1 2 3 2 first entry 07/05/2019 13:31:31.268032
2 rows selected.
You are inserting into the Marks when you insert into the Marks (the insert statement in the trigger before inserting) and so on in a recursive way. Hence the direct cause of error.

Investigating ORA-00001 in PL/SQL

We've run into ORA-00001 while executing a stored procedure in production last time.
The stored procedure was working fine until yesterday, and I tried to troubleshoot but didn't get anywhere.
Appreciate if you can provide some useful thoughts on below..
I've breakdown the situation into:
1) Table with primary key,
2) A sequence
3) A stored procedure.
1) We have main table as below:
CREATE TABLE MY_MESSAGES (MESSAGE_ID NUMBER, MESSAGE VARCHAR2(200));
CREATE UNIQUE INDEX MY_MESSAGES_PK ON MY_MESSAGES (MESSAGE_ID);
ALTER TABLE MY_MESSAGES ADD CONSTRAINT MY_MESSAGES_PK PRIMARY KEY (MESSAGE_ID) USING INDEX ENABLE;
2) A sequence
CREATE SEQUENCE MESSAGE_ID_SEQUENCE;
An independent Backup table:
CREATE TABLE MY_MESSAGES_BKP (BKP_ID VARCHAR2(200), RECIVED_TIME TIMESTAMP, MESSAGE VARCHAR2(200));
INSERT INTO MY_MESSAGES_BKP VALUES('201', TIMESTAMP '2018-09-26 00:00:00.000000', 'MSG206');
INSERT INTO MY_MESSAGES_BKP VALUES('202', TIMESTAMP '2018-09-26 05:00:00.000000', 'MSG206');
INSERT INTO MY_MESSAGES_BKP VALUES('203', TIMESTAMP '2018-09-26 06:00:00.000000', 'MSG207');
INSERT INTO MY_MESSAGES_BKP VALUES('204', TIMESTAMP '2018-09-26 07:00:00.000000', 'MSG208');
INSERT INTO MY_MESSAGES_BKP VALUES('205', TIMESTAMP '2018-09-26 08:00:00.000000', 'MSG209');
COMMIT;
3) And finally, the stored procedure:
DECLARE
TYPE VARCHAR_TABLE IS TABLE OF VARCHAR(200);
V_MESSAGE_ID NUMBER(20) := 0;
V_BKP_IDS VARCHAR_TABLE := VARCHAR_TABLE();
V_EXC_QUERY VARCHAR2(200) := 'INSERT INTO MY_MESSAGES(MESSAGE_ID, MESSAGE) SELECT :1, MESSAGE FROM MY_MESSAGES_BKP WHERE BKP_ID = :2';
BEGIN
SELECT BKP_ID BULK COLLECT INTO V_BKP_IDS FROM MY_MESSAGES_BKP WHERE RECIVED_TIME > TIMESTAMP '2018-09-26 00:00:00.000000';
FOR I IN 1..V_BKP_IDS.COUNT LOOP
EXECUTE IMMEDIATE 'SELECT MESSAGE_ID_SEQUENCE.NEXTVAL FROM DUAL' INTO V_MESSAGE_ID ;
EXECUTE IMMEDIATE V_EXC_QUERY USING V_MESSAGE_ID, V_BKP_IDS(I);
END LOOP;
V_BKP_IDS.DELETE;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/
Knowing that, the table MY_MESSAGES is used by other processes that uses the same sequence (MESSAGE_ID_SEQUENCE) for the primary key..
The stored procedure was running for some time and it inserted more than 400 records out of ~5000 records.. then it stopped with below error:
ORA-00001: unique constraint my_messages_pk) violated
Moreover, after investigating the table we've found that all primary keys of the successfully inserted records were sequential before it stopped..
The other process was running perfectly fine in the background, and we can see the inserted records are also sequential but with 1 number gap between last record inserted by the stored procedure and the next inserted record by the other process.
Which means that the MESSAGE_ID_SEQUENCE.NEXTVAL was executed by the stored procedure, but no record was inserted.
The tables doesn't have a record with a primary key were the stored procedure stopped.
What could have possibly gone wrong?
And how can we investigate further?
the most straight forward explanation would be that in the case of the ORA-00001, this select SELECT :1, MESSAGE FROM MY_MESSAGES_BKP WHERE BKP_ID = :2 found more than one row with BKP_ID = :2. This would in turn cause the insert INSERT INTO MY_MESSAGES(MESSAGE_ID, MESSAGE) SELECT :1, MESSAGE FROM MY_MESSAGES_BKP WHERE BKP_ID = :2 to use the same number from the seuquence several times. HTH

Oracle trigger on varchar values not working

I have 2 tables 'label' and 'musician'
CREATE TABLE label
(labId varchar(10) NOT NULL PRIMARY KEY,
labName varchar(20) NOT NULL
);
CREATE TABLE musician
(musId varchar(10) NOT NULL PRIMARY KEY,
musName varchar(30) NOT NULL,
labId varchar(10) NOT NULL,
CONSTRAINT MusLabel FOREIGN KEY (labId) REFERENCES label(labId)
);
I created a trigger to limit the number of musicians a label can have within a
range of 1 to 5; so that for example a label x cannot have 6 musicians:
CREATE OR REPLACE TRIGGER before_musician_insert
BEFORE INSERT ON musician
FOR EACH ROW
DECLARE
total integer;
BEGIN
SELECT COUNT(*) INTO total
FROM musician, label
WHERE musician.labId=label.labId;
IF (total < 0 OR total > 5)
THEN
DBMS_OUTPUT.PUT_LINE('Invalid');
END IF;
END;
/
When I insert a 6th musician into the table with the same label ID, the insert statement does not 'trigger' the TRIGGER and the 6th value is added to the table.
I don't know how to fix this.
I tried a check constraint but with varchar values, it is not working either.
I appreciate your help.
Your code has multiple issues. For instance, it is not accessing :new. The trigger is on the wrong table. It has no error generation.
I might suggest something like this:
CREATE OR REPLACE TRIGGER before_labels_insert
BEFORE INSERT ON labels
FOR EACH ROW
DECLARE
v_total integer;
user_xcep EXCEPTION;
PRAGMA EXCEPTION_INIT( user_xcep, -20001 );
BEGIN
SELECT COUNT(*) INTO v_total
FROM labels l
WHERE l.labId = :new.labId;
IF (v_total >= 5) THEN
DBMS_OUTPUT.PUT_LINE('Invalid');
RAISE user_xcep
END IF;
END;
Your trigger fires BEFORE the insert, so the sixth entry doesn't exist when the query is executed.
If you want to error on the insert of the 6th entry, you can make it an AFTER trigger (and fire once per statement, rather than FOR EACH ROW).

Oracle 'statement level' Trigger

I want to create a Statement level Trigger which means I want to insert only one record into table EMP_AUDIT when 1 or more rows are inserted into table EMP. For example: if I have 10 records inserted into EMP, then only 1 record should be inserted into EMP_AUDIT table.
There are no constraints on columns. (i.e. can be NULL)
I tried to use the following trigger but it gives me the Error(2,2): PL/SQL: SQL Statement ignored
Error(2,14): PL/SQL: ORA-00947: not enough values
CREATE OR REPLACE
TRIGGER TRIG_EMP AFTER INSERT ON EMP
BEGIN
INSERT INTO EMP_AUDIT
VALUES (TRANID,EMPNUM,SYSDATE);
END;
CREATE TABLE EMP
(TRANID NUMBER,
EMPNUM VARCHAR2(100),
EMPLOC VARCHAR2(100));
CREATE TABLE EMP_AUDIT
(EVENTID NUMBER,
EMPNUM VARCHAR2(100),
ENTRDATE DATE);
The statement-level trigger (which you have) cannot see the data that was inserted. After all, if there were 10 rows inserted, what values should the columns be for your audit table?
You need a row-level trigger for this to work, e.g.:
CREATE OR REPLACE
TRIGGER TRIG_EMP
AFTER INSERT ON EMP
FOR EACH ROW
BEGIN
INSERT INTO EMP_AUDIT
VALUES (:NEW.TRANID,:NEW.EMPNUM,:NEW.SYSDATE);
END;
Use this piece of code:
CREATE OR REPLACE TRIGGER
TRIG_EMP
AFTER INSERT ON EMP
FOR EACH ROW
BEGIN
INSERT INTO EMP_AUDIT
VALUES (:NEW.TRANID,:NEW.EMPNUM,:NEW.SYSDATE);
END;

Resources