trigger to override the message to error(ORA-02292) - sql

I want override the message that generate error (ORA-02292).
It is message like that
ORA-02292: integrity constraint (IVANKA.FK_SUPPLIER) violated - child record found
I want a trigger to override the above message to his example on this (MY override :))
I tried do like that
for first create table
CREATE TABLE supplier
( supplier_id numeric(10) not null,
supplier_name varchar2(50) not null,
contact_name varchar2(50),
CONSTRAINT supplier_pk PRIMARY KEY (supplier_id)
);
CREATE TABLE products
( product_id numeric(10) not null,
supplier_id numeric(10) not null,
CONSTRAINT fk_supplier
FOREIGN KEY (supplier_id)
REFERENCES supplier (supplier_id)
);
then insert data
INSERT INTO supplier
(supplier_id, supplier_name, contact_name)
VALUES (1000, 'Microsoft', 'Bill Gates');
INSERT INTO products
(product_id, supplier_id)
VALUES (50000, 1000);
then do trigger
create or replace trigger sup_z
after delete on supplier
for each row
declare
v_error_constraint exception;
pragma exception_init(v_error_constraint, -2292);
begin
null;
exception
When v_error_constraint then
raise_application_error(-20001,
'My ovvervide:)');
End;
then do delete to generate message
DELETE from supplier
WHERE supplier_id = 1000
but I see not my message in trigger I see
ORA-02292: integrity constraint (IVANKA.FK_SUPPLIER) violated - child record found
Can you help me? What am I doing wrong?

Your trigger is fired after the DELETE has been done, so it will never fire if the DELETE gives an error.
You could use a BEFORE trigger to avoid the DELETE, if you find some child records; for example:
create or replace trigger sup_z
before delete on supplier
for each row
declare
vNumberOfChild number;
begin
select count(1)
into vNumberOfChild
from products
where supplier_id = :old.supplier_id;
--
if vNumberOfChild > 0 then
raise_application_error(-20001, 'My ovveride:)');
end if;
End;
Another way could be defining a procedure to handle the delete:
SQL> create or replace procedure deleteSupplier(p_id in numeric) is
2 vNumberOfChild number;
3 begin
4 select count(1)
5 into vNumberOfChild
6 from products
7 where supplier_id = p_id;
8 --
9 if vNumberOfChild > 0 then
10 dbms_output.put_line('My ovveride');
11 else
12 delete supplier
13 where supplier_id = p_id;
14 end if;
15 end;
16 /
Procedure created.
SQL> set serveroutput on
SQL> exec deleteSupplier(1000);
My ovveride
PL/SQL procedure successfully completed.
This avoids running the delete checking the data before, so you need no triggers.

Check this out (Display custom message when constraint is violated PL/SQL). I think you want something similar.

When all you need is just the message to be printed, why do we need a trigger in the first place? Shouldn't we be handling this just with an exception alone?
declare
v_error_constraint exception;
pragma exception_init(v_error_constraint, -2292);
begin
DELETE from supplier
WHERE supplier_id = 1000;
exception
When v_error_constraint then
raise_application_error(-20001,
'My ovvervide:)');
End;

Related

sql oracle - constraint on 2 columns from different tables

I have designed a ticket system booking for flights. I want to add a constraint such that the number of tickets you can insert to be less than number of seats from a flight plane.
Let's say I inserted a flight with a plane with 10 seats. I can insert only 10 tickets for that particular flight. Otherwise, an error message should appear.
I tried to make a trigger using the count function on flight number.
CREATE OR REPLACE TRIGGER trg_ticket_BRIU
BEFORE INSERT OR UPDATE ON Ticket
FOR EACH ROW
DECLARE
l_numberofseats flight.numberofseats%type;
BEGIN
select numberofseats into l_numberofseats
from flight
where flightnumber=:new.flightnumber;
IF :new.count(flightnumber) > l_numberofseats
THEN
raise_application_error(-2000, 'Not enough seats');
END IF;
END;
but I get this error
Trigger TRG_TICKET_BRIU compiled
LINE/COL ERROR
--------- -------------------------------------------------------------
8/5 PLS-00049: bad bind variable 'NEW.COUNT'
Errors: check compiler log
Personally, I would add an AIRCRAFT and a SEAT table:
CREATE TABLE aircraft (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT aircraft__id__pk PRIMARY KEY,
tail_number VARCHAR2(6)
NOT NULL
CONSTRAINT aircraft__tn__u UNIQUE
CONSTRAINT aircraft__tn_chk CHECK(
REGEXP_LIKE(
tail_number,
'[A-Z]\d{1,5}|[A-Z]\d{1,4}[A-Z]|[A-Z]\d{1,3}[A-Z]{2}'
)
),
manufacturer VARCHAR2(20)
NOT NULL,
model VARCHAR2(20)
NOT NULL,
airline_id CONSTRAINT aircraft__aid__fk REFERENCES airline(airline_id)
NOT NULL
);
CREATE TABLE seat (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT seat__id__pk PRIMARY KEY,
aircraft_id CONSTRAINT seat__aid__fk REFERENCES aircraft(id)
NOT NULL,
seat_row VARCHAR2(3)
NOT NULL,
seat_column NUMBER
NOT NULL,
CONSTRAINT seat__aid_r_c__u UNIQUE (aircraft_id, seat_row, seat_column)
);
Then your flight table would reference the aircraft:
CREATE TABLE flight (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT flight__id__pk PRIMARY KEY,
aircraft_id CONSTRAINT flight__aid__fk REFERENCES aircraft(id)
NOT NULL
-- ...
);
And the ticket would reference a flight and a seat:
CREATE TABLE ticket (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT ticket__id__pk PRIMARY KEY,
flight_id CONSTRAINT ticket__fid__fk REFERENCES flight(id)
NOT NULL,
seat_id CONSTRAINT ticket__sid__fk REFERENCES seat(id)
NOT NULL,
-- ...
CONSTRAINT ticket__fid_sid__u UNIQUE (flight_id, seat_id)
);
Then you can never sell a seat that does not exist on an aircraft and do not need to count the maximum number of tickets and compare it to seats (and the seat has added attributes like its location on the plane that can be displayed on the ticket).
All you need then is to ensure the referential consistency that, for a ticket, the flight and the seat are on the same aircraft; which can be done with a trigger:
CREATE TRIGGER ticket_check_seat_on_flight
BEFORE INSERT OR UPDATE ON ticket
FOR EACH ROW
DECLARE
is_valid NUMBER(1);
BEGIN
SELECT 1
INTO is_valid
FROM flight f
INNER JOIN seat s
ON (f.aircraft_id = s.aircraft_id)
WHERE f.id = :NEW.flight_id
AND s.id = :NEW.seat_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(
-20000,
'Flight and seat are on different aircraft.'
);
END;
/
db<>fiddle here
You can use an AFTER STATEMENT trigger:
CREATE TRIGGER ticket__check_number_of_seats
AFTER INSERT OR UPDATE OR DELETE ON ticket
DECLARE
is_invalid NUMBER(1,0);
BEGIN
SELECT 1
INTO is_invalid
FROM flight f
INNER JOIN (
SELECT flight_id,
COUNT(*) AS tickets_sold
FROM ticket
GROUP BY flight_id
) t
ON f.id = t.flight_id
WHERE t.tickets_sold > f.number_of_seats;
RAISE_APPLICATION_ERROR(
-20000,
'Too many tickets sold for flight.'
);
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
/
It could be made more efficient by using a compound trigger to collate, for each row, the flight_id values into a collection and then, after the statement, only checking the number of tickets for those flights; however, I'll leave that extension as an exercise for the OP.
db<>fiddle here
As others indicated there is no :new.count column. This is because :new (and :old) create a pseudo-row containing exactly the same columns as the table definition. Further you will get a Mutating exception as what you need to count in the flight_number from tickets. However, since that is the table causing he trigger to fire you cannot reference it. So what to do: create a compound trigger, and a supporting Type (nested table). Within it use the after row section to capture the flight_numbers processed. Then in the after statement section you can select count of tickets for each flight. If that count > 0 then raise your exception. ( see Demo )
create type flight_tickets_ntt
is table of integer;
create or replace trigger trg_ticket_ciu
for update or insert on tickets
compound trigger
l_flights flight_tickets_ntt := flight_tickets_ntt();
after each row is
begin
if :new.flight_number not member of l_flights then
l_flights.extend ;
l_flights(l_flights.count) := :new.flight_number;
end if;
end after each row;
after statement is
l_flight_cnt flight.flight_number%type;
begin
select count(*)
into l_flight_cnt
from flight f
where f.number_of_seats <
( select count(*)
from tickets t
where t.flight_number in
( select *
from table (l_flights)
)
);
if l_flight_cnt > 0 then
raise_application_error(-20000, 'Not enough seats');
end if;
end after statement;
end trg_ticket_ciu;
There remains a you need to handle: What happens if an update changes the flight number or perhaps (missing column) the data of the flight.

Enabling and disabling a trigger inside another trigger

I got a table Location
CREATE TABLE Location (
idL INTEGER,
City VARCHAR2(15) NOT NULL,
Street VARCHAR2(35) NOT NULL,
Nation CHAR(6) NOT NULL,
CONSTRAINT PK_idL PRIMARY KEY(idL)
);
And a table Person
CREATE TABLE Person(
p_Name VARCHAR2(20) NOT NULL,
p_Surname VARCHAR2(20) NOT NULL,
idP INTEGER,
b_Date DATE NOT NULL,
id_PL INTEGER,
CONSTRAINT PK_idP PRIMARY KEY(idP),
CONSTRAINT FK_idPL FOREIGN KEY(id_PL) REFERENCES Location(idL)
);
I calculate the primary key "automatically" as it follows:
CREATE SEQUENCE seq_loc_pk
start with 1
increment by 1;
CREATE OR REPLACE TRIGGER auto_pk_loc
BEFORE INSERT ON Location
FOR EACH ROW
BEGIN
:new.idL := seq_loc_pk.nextval;
END;
/
Now I want to insert the residence for a new person (after I've created the right view of course) with an instead of trigger like this:
CREATE OR REPLACE TRIGGER newperson
INSTEAD OF INSERT ON Residence
FOR EACH ROW
DECLARE
nl Loc.idL%TYPE;
BEGIN
ALTER TRIGGER auto_pk_loc DISABLE; -- Error
nl := seq_loc_pk.nextval;
:NEW.idL := nl;
INSERT INTO Location VALUES(:NEW.City,:NEW.Street,:NEW.Nation);
INSERT INTO Patient VALUES(:NEW.P_Name,:NEW.P_Surname,:NEW.B_Date,,nl);
ALTER TRIGGER auto_pk_loc ENABLE;
END;
/
I thought about disabling and enabling the trigger auto_pk_loc so that it doesn't create extra values for no reason, but I think this is not the right way to do it? What is it though? Thanks for whoever answers.
You can do this by placing it in execute immedaite:
BEGIN
execute immedidate 'ALTER TRIGGER auto_pk_loc DISABLE';
nl := seq_loc_pk.nextval;
:NEW.idL := nl;
INSERT INTO Location VALUES(:NEW.City,:NEW.Street,:NEW.Nation);
INSERT INTO Patient VALUES(:NEW.P_Name,:NEW.P_Surname,:NEW.B_Date,,nl);
execute immedidate 'ALTER TRIGGER auto_pk_loc ENABLE';
END;
/
But this will cause you all sorts of issues; DDL commits so you'll have to make this an autonomous transaction and you'll hit concurrency problems. This is best avoided.
A better method is to use the returning clause to fetch the value you just inserted:
BEGIN
INSERT INTO Location VALUES(:NEW.City,:NEW.Street,:NEW.Nation)
returning idl into nl;
INSERT INTO Patient VALUES(:NEW.P_Name,:NEW.P_Surname,:NEW.B_Date,nl);
END;
/
Though as #astentx noted, you probably want to use merge to avoid having duplicate locations. This doesn't support returing, so you'll have to use some combination of insert+update instead.
Finally - assuming you're on 12c or higher - it's better to use an identity column or sequence default to auto-generate the location IDs over a trigger.

ORA-04091: table name is mutating - when trigger from child table wants to update parent table

I have 2 simple tables:
CREATE TABLE ORDERS
( ORDER_KEY number(10) NOT NULL,
ORDER_NR varchar2(50) NOT NULL,
LAST_UPDATE DATE,
CONSTRAINT ORDERS_PK PRIMARY KEY (ORDER_KEY)
);
CREATE TABLE ORDER_POSITIONS
( ORDER_POS_KEY number(10) NOT NULL,
ORDER_POS_NR number(10),
ORDER_POS_DESCRIPTION varchar2(50),
ORDER_KEY NUMBER(10) NOT NULL,
CONSTRAINT ORDER_POSITIONS_PK PRIMARY KEY (ORDER_POS_KEY),
CONSTRAINT ORDERS_FK
FOREIGN KEY (ORDER_KEY)
REFERENCES ORDERS(ORDER_KEY)
ON DELETE CASCADE
);
On the table ORDER_POSITIONS I created a trigger which should update the column LAST_UPDATE whenever a position is deleted.
CREATE OR REPLACE TRIGGER TGAUDIT_ORDER_POS
AFTER DELETE
ON ORDER_POSITIONS
FOR EACH ROW
DECLARE
BEGIN
UPDATE ORDERS O SET O.LAST_UPDATE = SYSDATE WHERE O.ORDER_KEY = :OLD.ORDER_KEY;
END;
If I delete a position from the table ORDER_POSITION, everything is working fine (the column LAST_UPDATE is updated).
However, if I want to delete an order, all its positions are gonna be deleted, too (via CASCADE DELETE). At this moment also the trigger on the table ORDER_POSITIONS is being raised and it wants to update the column of the table which is currently being deleted - ORDERS. Obviously I get here the error : ORA-04091 Table ORDERS is mutating.
Is there a way to get it solved?
I solved it finally using a compound trigger:
CREATE OR REPLACE TRIGGER TGAUDIT_ORDER_POS
FOR DELETE ON ORDER_POSITIONS
COMPOUND TRIGGER
TYPE parent_key_type IS TABLE OF ORDERS.ORDER_KEY%TYPE;
parent_keys parent_key_type := parent_key_type();
AFTER EACH ROW IS BEGIN
IF DELETING THEN
BEGIN
parent_keys.extend;
parent_keys(parent_keys.last) := :old.ORDER_KEY;
END;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT IS BEGIN
FOR i IN 1..parent_keys.count LOOP
UPDATE DEVART_TEST.ORDERS O SET O.LAST_UPDATE = SYSDATE WHERE O.ORDER_KEY = parent_keys(i);
END LOOP;
END AFTER STATEMENT;
END;
UPDATE: Another option would be to catch this specific exception within the regular trigger.
CREATE OR REPLACE TRIGGER TGAUDIT_ORDER_POS
AFTER DELETE
ON ORDER_POSITIONS
FOR EACH ROW
DECLARE
TABLE_MUTATING EXCEPTION;
PRAGMA EXCEPTION_INIT(TABLE_MUTATING, -4091 );
BEGIN
UPDATE ORDERS O SET O.LAST_UPDATE = SYSDATE WHERE O.ORDER_KEY = :OLD.ORDER_KEY;
EXCEPTION
WHEN TABLE_MUTATING THEN
NULL; -- suppress
END;
I would recreate the foreign-key constraint without the ON DELETE CASCADE clause and delete all order positions before deleting an order. That way you avoid the mutating table error by not having two tables mutating at the same time.

Trigger function not working/ SQL statement ignored

Im working on a database for school, everything is working except the triggers.
For example:
create or replace TRIGGER T_CATEGORY
before insert on GAMECATEGORY
for each row
BEGIN
select G_Category_ID_SEQ.nextval into new.Category_ID from dual;
END;
its giving the SQL statement ignored and its not creating the triggers?
Is there anyone who can help me? :D
:EDIT
CREATE TABLE LIVESTREAM
(
Stream_ID number(13),
LivestreamURL varchar2(50),
primary key(Stream_ID, LivestreamURL),
Channel_ID number(13) not null,
Category_ID number(13) not null,
Title varchar2(50) not null,
Organisation_Name varchar2(50),
Viewers number(13) not null,
LivestreamStatus varchar2(10) check (UPPER(LivestreamStatus) IN ('ONLINE','OFFLINE')),
Followers number(13) not null,
"Views" number(13) not null
);
ALTER TABLE LIVESTREAM ADD constraint FK_LIVESTREAM_Category_ID foreign key(Category_ID) REFERENCES GAMECATEGORY(Category_ID)on delete cascade;
ALTER TABLE LIVESTREAM ADD constraint FK_LIVESTREAM_Channel_ID foreign key(Channel_ID) REFERENCES USERCHANNEL(Channel_ID)on delete cascade;
CREATE SEQUENCE LIVESTREAM_Stream_ID_SEQ
start with 1
increment by 1;
CREATE OR REPLACE TRIGGER T_LIVESTREAM
before insert on LIVESTREAM
for each row
BEGIN
select LIVESTREAM_Stream_ID_SEQ.nextval into :new.Stream_ID from dual;
END;
/
When i insert the data into the table it gives me this error:
INSERT INTO LIVESTREAM(LivestreamURL, Channel_ID, Category_ID, Title, Organisation_Name, Viewers, LivestreamStatus, Followers, "Views")
VALUES('http://www.twitch.tv/nightblue3, 2, 1,Next Stream: Friday # 4 AM PST / 7 AM EST / NOON GMT, The Round Table, , OFFLINE, 1052215, 115257581')
Error at Command Line : 253 Column : 1
Error report -
SQL Error: ORA-00947: not enough values
00947. 00000 - "not enough values"
*Cause:
*Action:
Check out the new should be :new.
The reason is you're inserting this whole string: 'http://www.twitch.tv/nightblue3, 2, 1,Next Stream: Friday # 4 AM PST / 7 AM EST / NOON GMT, The Round Table, , OFFLINE, 1052215, 115257581'
into the LivestreamURL column alone. You must change your query to this:
INSERT INTO LIVESTREAM(LivestreamURL,
Channel_ID, Category_ID,
Title, Organisation_Name,
Viewers, LivestreamStatus,
Followers, "Views")
VALUES
('http://www.twitch.tv/nightblue3',
2, 1,
'Next Stream: Friday # 4 AM PST / 7 AM EST / NOON GMT',
'The Round Table', ,
'OFFLINE', 1052215,
115257581)
Then change your triggers into this:
CREATE TRIGGER T_LIVESTREAM
before insert on LIVESTREAM
for each row
BEGIN
if :new.Stream_ID is null then
:new.Stream_ID := LIVESTREAM_Stream_ID_SEQ.nextval;
end if;
END;
/
create or replace TRIGGER T_CATEGORY
before insert on GAMECATEGORY
for each row
BEGIN
if :new.Category_ID is null then
:new.Category_ID := G_Category_ID_SEQ.nextval;
end if;
END;
/

Oracle SQL Triggers, error if both in one file, compiles well when in separate

I'm new to Oracle. I run my scripts through SQL Developer. I try to make few connected tables and two triggers to them.
CREATE TABLE BIKE(
ID NUMBER(8) NOT NULL,
FRONT_WHEEL_ID NUMBER(8),
BACK_WHEEL_ID NUMBER(8)
);
CREATE TABLE WHEEL(
ID NUMBER(8) NOT NULL,
DIAMETER NUMBER(8,2)
);
CREATE TABLE PRODUCTS(
ID NUMBER(8) NOT NULL,
PRODUCT_NAME NVARCHAR2(64),
PRODUCT_COUNT NUMBER(8)
);
CREATE TABLE PRODUCTS_HISTORY(
ID NUMBER(8) NOT NULL,
DIAMETER NUMBER(8,2)
);
-- PK's
ALTER TABLE BIKE
ADD CONSTRAINT BIKE_PK PRIMARY KEY (ID);
ALTER TABLE WHEEL
ADD CONSTRAINT WHEEL_PK PRIMARY KEY (ID);
ALTER TABLE PRODUCTS
ADD CONSTRAINT PRODUCTS_PK PRIMARY KEY (ID);
ALTER TABLE PRODUCTS_HISTORY
ADD CONSTRAINT PRODUCTS_HISTORY_PK PRIMARY KEY (ID);
-- FK's
ALTER TABLE BIKE
ADD CONSTRAINT BIKE_FRONT_WHEEL_FK FOREIGN KEY (FRONT_WHEEL_ID)
REFERENCES WHEEL (ID);
ALTER TABLE BIKE
ADD CONSTRAINT BIKE_BACK_WHEEL_FK FOREIGN KEY (BACK_WHEEL_ID)
REFERENCES WHEEL (ID);
-- Simple data
INSERT INTO PRODUCTS (ID, PRODUCT_NAME) VALUES (1, 'Bikes');
INSERT INTO PRODUCTS (ID, PRODUCT_NAME) VALUES (2, 'Wheels');
-- Triggers
CREATE OR REPLACE TRIGGER WHEEL_after_insert
AFTER INSERT OR DELETE
ON WHEEL
DECLARE
WHEEL_COUNT NUMBER(8);
BEGIN
SELECT COUNT(*) INTO WHEEL_COUNT FROM WHEEL;
UPDATE PRODUCTS SET PRODUCT_COUNT = WHEEL_COUNT WHERE PRODUCT_NAME = 'Wheels';
END;
CREATE OR REPLACE TRIGGER BIKE_after_insert
AFTER INSERT OR DELETE
ON BIKE
DECLARE
BIKE_COUNT NUMBER(8);
BEGIN
SELECT COUNT(*) INTO BIKE_COUNT FROM BIKE;
UPDATE PRODUCTS SET PRODUCT_COUNT = BIKE_COUNT WHERE PRODUCT_NAME = 'Bikes';
END;
When I run whole as one sql script I get:
...
1 rows inserted.
TRIGGER WHEEL_AFTER_INSERT compiled
Errors: check compiler log
When I run everything except :
CREATE OR REPLACE TRIGGER BIKE_after_insert
AFTER INSERT OR DELETE
ON BIKE
DECLARE
BIKE_COUNT NUMBER(8);
BEGIN
SELECT COUNT(*) INTO BIKE_COUNT FROM BIKE;
UPDATE PRODUCTS SET PRODUCT_COUNT = BIKE_COUNT WHERE PRODUCT_NAME = 'Bikes';
END;
And then run the above in separate worksheet everything works fine.
What shall I add ? I tried adding COMMIT; and COMMIT WORK; between triggers, but it didn't do any difference.
Jah bless ya for help
Program blocks (including triggers declarations) should be separated with as forward slash (/) if you have multiple in the same script. In general, it's best to add a / after each PL/SQL block. You don't need them between SQL statements.
...
CREATE OR REPLACE TRIGGER WHEEL_after_insert
AFTER INSERT OR DELETE
ON WHEEL
DECLARE
WHEEL_COUNT NUMBER(8);
BEGIN
SELECT COUNT(*) INTO WHEEL_COUNT FROM WHEEL;
UPDATE PRODUCTS SET PRODUCT_COUNT = WHEEL_COUNT WHERE PRODUCT_NAME = 'Wheels';
END;
/
CREATE OR REPLACE TRIGGER BIKE_after_insert
AFTER INSERT OR DELETE
ON BIKE
DECLARE
BIKE_COUNT NUMBER(8);
BEGIN
SELECT COUNT(*) INTO BIKE_COUNT FROM BIKE;
UPDATE PRODUCTS SET PRODUCT_COUNT = BIKE_COUNT WHERE PRODUCT_NAME = 'Bikes';
END;
/