I have to check before inserting or updating a certain relation whether a particular row attribute in relation A is equal or greater than a corresponding row attribute in relation B. I wrote a trigger:
Attempt:
CREATE TRIGGER CandyCons AFTER INSERT, UPDATE ON Rel_A
AS
IF EXISTS (
SELECT * FROM Rel_A A, Rel_B B WHERE A.ID = B.ID AND A.candy > B.too_much
)
BEGIN
RAISERROR ('Stop eating!', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END;
I can't execute this. When I run it, I start getting line numbers like:
query
12
13
14
15
......... and so on
What should I do?
This can be done with a row level TRIGGER as in the following example.
First create the tables:
CREATE TABLE REL_B(ID NUMBER NOT NULL PRIMARY KEY, TOO_MUCH NUMBER);
CREATE TABLE REL_A(ID NUMBER NOT NULL REFERENCES REL_B(ID), CANDY NUMBER);
Then add some test data:
INSERT INTO REL_B VALUES (1,10);
INSERT INTO REL_B VALUES (2,20);
COMMIT;
Then create your TRIGGER:
CREATE OR REPLACE TRIGGER CANDYCONS
AFTER INSERT OR UPDATE ON REL_A
FOR EACH ROW
DECLARE
V_COUNT_TOO_MUCH_CANDY NUMBER := 0;
BEGIN
SELECT COUNT(*) INTO V_COUNT_TOO_MUCH_CANDY
FROM REL_B
WHERE REL_B.ID = :NEW.ID
AND :NEW.CANDY > REL_B.TOO_MUCH;
IF (V_COUNT_TOO_MUCH_CANDY > 0)
THEN
RAISE_APPLICATION_ERROR(-20144,'Stop eating!');
END IF;
END;
/
Then test:
These are ok:
INSERT INTO REL_A VALUES (1,9);
INSERT INTO REL_A VALUES (1,10);
But this is blocked:
INSERT INTO REL_A VALUES (1,11);
Error starting at line : 1 in command -
INSERT INTO REL_A VALUES (1,11)
Error report -
ORA-20144: Stop eating!
On the line after END;, enter a single slash (/) to tell SQLPlus to execute the current command buffer.
See also When do I need to use a semicolon vs a slash in Oracle SQL?
(There may be other issues with your code, and the other answers may address them. But I believe this is your immediate problem in terms of actually telling SQLPlus to compile and create the trigger.)
I believe you intend this:
CREATE TRIGGER CandyCons ON Rel_A
BEFORE INSERT, UPDATE
AS
IF EXISTS (SELECT 1 FROM Rel_B B WHERE :NEW.ID = B.ID AND :NEW.candy > B.too_much
)
BEGIN
RAISERROR ('Stop eating!', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END;
Wait. Oracle doesn't support if exists. And it requires then. So:
CREATE TRIGGER CandyCons ON Rel_A BEFORE INSERT, UPDATE
DECLARE
v_cnt number;
BEGIN
SELECT COUNT(*) INTO v_cnt
FROM Rebl_B B
WHERE :NEW.ID = B.ID AND :NEW.candy > B.too_much;
IF v_cnt = 0 THEN
BEGIN
RAISERROR ('Stop eating!', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END;
END;
Related
I try to create a custom table, which is created properly. The last 2 fields are matching the type from Orders table. The trigger's idea is when a certain date field is changed to INSERT this into the new table. The trigger is created in Oracle correctly, but once the field in question changes I get ORA errors (below) and cannot find out why they appear.
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "shc.trigger_table_1", line 20
ORA-04088: error during execution of trigger 'shc.trigger_table_1'
I tried changing the datatypes, tried introducing an EXCEPTION, tried removing the :old.info23 check.
Here is the table I am creating:
CREATE TABLE table1(
id number generated by default as identity,
table_name varchar2(20),
field_changed varchar2(20),
old_value date,
new_value date,
changed_by varchar(20),
date_of_change date,
orderRef varchar(50),
Ref varchar2(256));
Here is the problematic trigger:
CREATE OR REPLACE TRIGGER trigger_table_1
AFTER UPDATE ON consignment
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
DECLARE
my_AEXTERNAUFTRAGSNR varchar2(50);
my_AUFTRAGSREFERENZ2 varchar2(256);
BEGIN
IF :OLD.ANKUNFTBELDATUMVON <> :NEW.ANKUNFTBELDATUMVON AND :old.info23='I' THEN
Select AEXTERNAUFTRAGSNR, AUFTRAGSREFERENZ2
Into my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2
From orders
Where orders.nr = :old.ordernr;
insert into table1(table_name, field_changed, old_value, new_value, changed_by, date_of_change, orderRef, Ref)
values ('consignment', 'ANKUNFTBELDATUMVON', :OLD.ANKUNFTBELDATUMVON, :NEW.ANKUNFTBELDATUMVON, sys_context('userenv','OS_USER'), SYSDATE, my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2);
END IF;
END;
END;
The immediate problem is that you don't commit your autonomous transaction. If you change it to commit at the end:
...
END IF;
END;
commit;
END;
/
then it will work - fiddle. (You don't need the nested block, but it doesn't stop it working... and you could do insert ... select ... to avoid needing any local variables...)
But as you can see from the db<>fiddle result, if the update on consignment is rolled back then the insert into table1 is retained, since that was committed independently.
If you remove the PRAGMA AUTONOMOUS_TRANSACTION; instead then that won't happen. Then you won't need to, and indeed can't, commit within the trigger. You would just need:
CREATE OR REPLACE TRIGGER trigger_table_1
AFTER UPDATE ON consignment
FOR EACH ROW
DECLARE
my_AEXTERNAUFTRAGSNR varchar2(50);
my_AUFTRAGSREFERENZ2 varchar2(256);
BEGIN
IF :OLD.ANKUNFTBELDATUMVON <> :NEW.ANKUNFTBELDATUMVON AND :old.info23='I' THEN
Select AEXTERNAUFTRAGSNR, AUFTRAGSREFERENZ2
Into my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2
From orders
Where orders.nr = :old.ordernr;
insert into table1(table_name, field_changed, old_value, new_value, changed_by, date_of_change, orderRef, Ref)
values ('consignment', 'ANKUNFTBELDATUMVON', :OLD.ANKUNFTBELDATUMVON, :NEW.ANKUNFTBELDATUMVON, sys_context('userenv','OS_USER'), SYSDATE, my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2);
END IF;
END;
/
or with insert ... select ...:
CREATE OR REPLACE TRIGGER trigger_table_1
AFTER UPDATE ON consignment
FOR EACH ROW
BEGIN
IF :OLD.ANKUNFTBELDATUMVON <> :NEW.ANKUNFTBELDATUMVON AND :old.info23='I' THEN
insert into table1(table_name, field_changed,
old_value, new_value,
changed_by, date_of_change,
orderRef, Ref)
select 'consignment', 'ANKUNFTBELDATUMVON',
:OLD.ANKUNFTBELDATUMVON, :NEW.ANKUNFTBELDATUMVON,
sys_context('userenv','OS_USER'), SYSDATE,
o.AEXTERNAUFTRAGSNR, o.AUFTRAGSREFERENZ2
from orders o
where o.nr = :old.ordernr;
END IF;
END;
/
fiddle
Incidentally, if you only want the trigger to fire if a specific column (or columns) was included in an update statement then you could do:
AFTER UPDATE OF ANKUNFTBELDATUMVON ON consignment
but you would still need to check it had actually changed. Your current check doesn't cover ANKUNFTBELDATUMVON being update to or from null, which is only potentially an issue if the column is nullable.
Hi I try to Insert value in the second trigger with new id from first trigger only if condition is fulfiled, but I'm stuck.
table1_trg works
CREATE TABLE table1 (
id NUMBER(9,0) NOT NULL,
subject VARCHAR2(200) NOT NULL,
url_address VARCHAR2(200) NOT NULL,
)
CREATE OR REPLACE TRIGGER table1_trg
BEFORE INSERT ON table1
FOR EACH ROW
BEGIN
SELECT table1_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
CREATE OR REPLACE TRIGGER table1_url
BEFORE INSERT ON table1
FOR EACH ROW
WHEN (NEW.subject = 'Task')
BEGIN
INSERT INTO CSB.table1 (url_address)
VALUES ('blabla.com?' || :new.id);
END;
/
I insert only subject but after that i receive exception that subject can not be null.
INSERT INTO corp_tasks_spec (subject) VALUES ('Task')
Any ideas how to resolve it?
You should not be inserting a new record into the same table, you should be modifying the column values for the row you're already inserting - which the trigger is firing against. You're getting the error because of that second insert - which is only specifying the URL value, not the subject or ID (though the first trigger would fire again and set the ID for that new row as well - so it complains about the subject).
Having two triggers on the same firing point can be difficult in old versions of Oracle as the order they fired wasn't guaranteed - so for instance your second trigger might fire before the first, and ID hasn't been set yet. You can control the order in later versions (from 11g) with FOLLOWS:
CREATE OR REPLACE TRIGGER table1_url
BEFORE INSERT ON table1
FOR EACH ROW
FOLLOWS table1_trg
WHEN (NEW.subject = 'Task')
BEGIN
:NEW.url_address := 'blabla.com?' || :new.id;
END;
/
This now fires after the first trigger, so ID is set, and assigns a value to the URL in this row rather than trying to create another row:
INSERT INTO table1 (subject) VALUES ('Task');
1 row inserted.
SELECT * FROM table1;
ID SUBJECT URL_ADDRESS
---------- ---------- --------------------
2 Task blabla.com?2
But you don't really need two triggers here, you could do:
DROP TRIGGER table1_url;
CREATE OR REPLACE TRIGGER table1_trg
BEFORE INSERT ON table1
FOR EACH ROW
BEGIN
:NEW.id := table1_seq.NEXTVAL; -- no need to select from dual in recent versions
IF :NEW.subject = 'Task' THEN
:NEW.url_address := 'blabla.com?' || :new.id;
END IF;
END;
/
Then that trigger generates the ID and sets the URL:
INSERT INTO table1 (subject) VALUES ('Task');
1 row inserted.
SELECT * FROM table1;
ID SUBJECT URL_ADDRESS
---------- ---------- --------------------
2 Task blabla.com?2
3 Task blabla.com?3
Of course, for anything except Task you'll have to specify the URL as part of the insert, or it will error as that is a not-null column.
Create sequence
CREATE SEQUENCE table1_SEQ
START WITH 1
MAXVALUE 100000
MINVALUE 1
NOCYCLE
NOCACHE
NOORDER;
CREATE TRIGGER
CREATE OR REPLACE TRIGGER table1_TRG
Before Insert
ON table1 Referencing New As New Old As Old
For Each Row
Declare V_Val Number;
Begin
Select table1_SEQ.NextVal into V_Val From Dual;
If Inserting Then
:New.id:= V_Val;
End If;
End;
/
I don't know what my problem when created new trigger.
Is my syntax correct? Thanks! Console Logging pane
p/s: This my console display when I try to insert values
CREATE OR REPLACE TRIGGER EX03_3
BEFORE INSERT ON HR.CHITIETDATHANG
FOR EACH ROW
DECLARE
TONGHANG NUMBER; -- Total Items
HANGHIENCO NUMBER; -- Items present
HANGDABAN NUMBER; -- Items was sales.
BEGIN
-- Get total Items
SELECT SUM(MH.SOLUONG) INTO TONGHANG
FROM HR.MATHANG MH;
-- Get total Items was sales
SELECT SUM(CTDH.SOLUONG) INTO HANGDABAN
FROM HR.CHITIETDATHANG CTDH;
-- Items present
HANGHIENCO := TONGHANG - HANGDABAN;
IF(HANGHIENCO >= HANGDABAN) THEN
HANGHIENCO := HANGHIENCO-1;
INSERT INTO HR.CHITIETDATHANG VALUES(:NEW.SOHOADON,:NEW.MAHANG,
:NEW.GIABAN,:NEW.SOLUONG,:NEW.MUCGIAMGIA);
ROLLBACK;
END IF;
NULL;
END;
Seem two critical mistakes
1) trigger tries to insert into HR.CHITIETDATHANG in the body of insert trigger of HR.CHITIETDATHANG.
2) to use rollback after an insert statement is useless.
Note : I can see nothing relevant to make raise no_data_found in those select statements. EX03_3 and EX04_4 are confused, as Kaushik Nayak says.
I've been working around this trigger and when I run the script it tells me the previous error message. I can't seem to figure out why it won't compile correctly, every pl/sql trigger tutorial seems to have the structure my trigger has. Code is the following:
create
or replace trigger new_artist before insert
on
Artist referencing new as nvartist declare counter number;
begin select
count( * ) into
counter
from
Performer
where
Stage_name = nvartist.Stage_name;
if counter > 0 then signal sqlstate '45000';
else insert
into
Artist
values(
nvartist.Stage_name,
nvartist.Name
);
insert
into
Performer
values(nvartist.Stage_name);
end if;
end;
It checks if the new artist already exists in its supertype (Performer), if it does exist it gives an error if it doesn't it inserts both into artist(Stage_name varchar2, Name varchar2) and Performer(Stage_name). Another subtype of Performer (and sibling to Artist) is Band(Stage_name), which in turn has a relationship with Artist. Why does the compiler yell at me for this trigger?
Thanks in advance
You may want to try this variant (I slightly modified names of your tables).
Creating tables with sample data:
CREATE table test_artist(
stage_name varchar2(100)
, name varchar2(100)
);
create table test_performer(
stage_name varchar2(100)
);
/*inserting test performer on which trigger will rise an error*/
insert into test_performer
select 'performer_1' from dual;
Creating trigger:
create or replace trigger new_artist
before insert
on TEST_ARTIST
referencing new as nvartist
for each row
declare
counter number;
begin
select count(*)
into counter
from test_performer
where Stage_name = :nvartist.Stage_name;
if counter > 0 then
--signal sqlstate '45000' ;
raise_application_error( -20001, 'No insertion with existing Performer');
else
/*you cant update the same table, in other case you'll get
ora-04091 table mutating error.
But nevertheless this values will be inserted by sql triggered this trigger.*/
--insert into test_artist values(:nvartist.Stage_name, :nvartist.Name);
insert into test_performer values(:nvartist.Stage_name);
end if;
end new_artist;
After that this insert will work, cause the is no 'performer_2' in 'test_performer' table:
insert into test_artist
select 'performer_2', 'name_2' from dual;
And this will fail:
insert into test_artist
select 'performer_1', 'name_1' from dual;
I have a script to create table and related structures
DROP TABLE CDR.ExtDL_JobStatus;
--
-- TABLE: CDR.ExtDL_JobStatus
--
CREATE TABLE CDR.ExtDL_JobStatus(
Id NUMBER(38, 0) NOT NULL,
ShortName NUMBER(38, 0) NOT NULL,
Description NUMBER(38, 0) NOT NULL,
CONSTRAINT PK_ExtDL_JobStatus PRIMARY KEY (Id)
)
;
SHOW ERRORS;
Declare NumOfSequences NUMBER :=0;
Begin
Select COUNT(*)
INTO NumOfSequences
FROM All_Sequences
WHERE 1=1
And upper (Sequence_Owner) = upper ('CDR')
And upper (Sequence_Name) = upper ('ExtDL_JobStatus_Seq');
If NumOfSequences > 0 Then
Execute IMMEDIATE 'DROP SEQUENCE CDR.ExtDL_JobStatus_Seq';
End If;
End;
/
CREATE SEQUENCE CDR.ExtDL_JobStatus_Seq
INCREMENT BY 1
START WITH 1
NOMAXVALUE
NOMINVALUE
;
/
SHOW ERRORS;
Declare NumOfTriggers NUMBER :=0;
Begin
SELECT COUNT(*)
INTO NumOfTriggers
FROM All_Triggers
WHERE 1=1
And upper (Owner) = upper ('CDR')
And upper (Trigger_Name) = upper ('ExtDL_JobStatus_SeqTrg');
If NumOfTriggers > 0 Then
Execute IMMEDIATE 'DROP TRIGGER CDR.ExtDL_JobStatus_SeqTrg';
End If;
End;
/
CREATE TRIGGER CDR.ExtDL_JobStatus_SeqTrg
BEFORE INSERT
ON CDR.ExtDL_JobStatus
FOR EACH ROW
WHEN (new.Id IS NULL)
BEGIN
SELECT ExtDL_JobStatus_Seq.nextval into :new.Id from dual;
END;
/
SHOW ERRORS;
insert into CDR.ExtDL_JobStatus (SHORTNAME, Description) VALUES (1, 1);
insert into CDR.ExtDL_JobStatus (SHORTNAME, Description) VALUES (1, 1);
insert into CDR.ExtDL_JobStatus (SHORTNAME, Description) VALUES (1, 1);
select * FROM CDR.ExtDL_JobStatus
When I run it with SHOW ERRORS (there are multiple places where this exists), it only creates the table and is done.
DROP TABLE CDR.ExtDL_JobStatus succeeded.
CREATE TABLE succeeded.
When I remove all the show errors, here is what is returned
DROP TABLE CDR.ExtDL_JobStatus succeeded.
CREATE TABLE succeeded.
anonymous block completed
CREATE SEQUENCE succeeded.
anonymous block completed
TRIGGER CDR.ExtDL_JobStatus_SeqTrg Compiled.
1 rows inserted
1 rows inserted
1 rows inserted
ID SHORTNAME DESCRIPTION
---------------------- ---------------------- ----------------------
1 1 1
2 1 1
3 1 1
3 rows selected
Why is the execution terminated in the first case with SHOW ERRORS?
Try removing the ';' after each SHOW ERRORS.
The SQL*Plus commands like SET, SHOW ERRORS, etc don't require a ; and it is possible that the presence of the ; may be re-running the previous command in the buffer (i.e. CREATE SEQUENCE) which would cause an 'Object already exists' error. (I am feeling slightly too lazy to fire up Oracle just to confirm this).
Personally, I always specify the full command - i.e.
SHOW ERRORS TRIGGER ExtDL_JobStatus_SeqTrg