PL/SQL: NO DATA FOUND while updating another table based on conditions - sql

So I have a column on my PAYMENT table that is called Status.. It has a foreign key of another table called reservation with Reservation_ID. The Reservation Table also has a status column and it will only get updated when there is a value in the status column of payment table. So If my status field in payment table has the value "Confirmed", the value for that particular Reservation_ID is supposed to turn to 1.. Otherwise 22. This is how I made the trigger:
CREATE OR REPLACE TRIGGER stats BEFORE INSERT OR DELETE OR UPDATE ON PAYMENT FOR EACH ROW
DECLARE
V_STATUS VARCHAR2(20);
BEGIN
SELECT Status INTO V_STATUS FROM PAYMENT INNER JOIN RESERVATION ON PAYMENT.Reservation_ID=RESERVATION.Reservation_ID WHERE PAYMENT.Reservation_ID=:NEW.Reservation_ID;
IF INSERTING AND V_STATUS='CONFIRMED' THEN
UPDATE RESERVATION SET status=1 WHERE Reservation_ID=:new.Reservation_ID;
ELSIF UPDATING AND V_STATUS='CONFIRMED' THEN
UPDATE RESERVATION SET status=1 WHERE Reservation_ID=:new.Reservation_ID;
ELSE
UPDATE RESERVATION SET status=22 WHERE Reservation_ID=:new.Reservation_ID;
END IF;
END;
So the trigger basically gets compiled but when I try inserting values inside Payment Table, I get the following error:
Error report -
ORA-01403: no data found
ORA-06512: at "ME.STATS", line 4
ORA-04088: error during execution of trigger 'ME.STATS'
create statments for both tables:
CREATE TABLE RESERVATION(RESERVATION_id NUMBER(10) NOT NULL, MEMBER_ID NUMBER(10) CONSTRAINT RE_MEM_fk REFERENCES MEMBER(MEMBER_ID) ON DELETE SET NULL,status NUMBER(10) CONSTRAINT RES_status_fk REFERENCES STATUS(RESERVATION_status_id) ON DELETE SET NULL, CONSTRAINT PK_BOOK PRIMARY KEY(RESERVATION_id));
CREATE TABLE PAYMENT(Payment_ID NUMBER(10) NOT NULL ,RESERVATION_id NUMBER(10) CONSTRAINT Pay_RES_fk REFERENCES RESERVATION(RESERVATION_id) ON DELETE SET NULL, TicketPrice NUMBER(10), ExtraFaciliFees Number(10),TOTAL_AMOUNT Number(10), PromotionalCode VARCHAR2(10), CONSTRAINT PK_PAY PRIMARY KEY(Payment_ID));

First :
CREATE TABLE RESERVATION(
Status NUMBER(10));
SELECT Status INTO V_STATUS
IF INSERTING AND V_STATUS='CONFIRMED'
Could you explain me how you expect a NUMBER to match a string ?
Next (from http://www.dba-oracle.com/sf_ora_01403_no_data_found.htm )
SELECT INTO clauses are standard SQL queries which pull a row or set
of columns from a database, and put the retrieved data into variables
which have been predefined.
If the SELECT INTO statement doesn't return at least on e row,
ORA-01403 is thrown.
So this :
SELECT
Status INTO V_STATUS
FROM PAYMENT p
INNER JOIN RESERVATION r
ON p.Reservation_ID = r.Reservation_ID
WHERE p.Reservation_ID = :NEW.Reservation_ID;
Is likely to output no row at all...

Agree with #Blag, below statement is giving the no data found exception.
In general if you want to know the exact line number the error is pointing to you can refer to the object via DBA_SOURCE or ALL_SOURCES.
SELECT
Status INTO V_STATUS
FROM PAYMENT p
INNER JOIN RESERVATION r
ON p.Reservation_ID = r.Reservation_ID
WHERE p.Reservation_ID = :NEW.Reservation_ID;

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.

Trigger after insert to check and compare records between table

In an Oracle Database, I need to create some trigger or procedure to treat this case in the most performative way possible (is an extremely large amount of data).
I have a table called ORDER_A that every day receives a full load (its truncated, and all records are inserted again).
I have a table called ORDER_B which is a copy of ORDER_A, containing the same data and some additional control dates.
Each insertion on ORDER_A must trigger a process that looks for a record with the same identifier (primary key: order_id) in table B.
If a record exists with the same order_id, and any of the other columns have changed, an update must be performed on table B
If a record exists with the same order_id, and no values ​​in the other columns have been modified, nothing should be performed, the record must remain the same in table B.
If there is no record with the same order_id, it must be inserted in table B.
My tables are like this
CREATE TABLE ORDER_A
(
ORDER_ID NUMBER NOT NULL,
ORDER_CODE VARCHAR2(50),
ORDER_STATUS VARCHAR2(20),
ORDER_USER_ID NUMBER,
ORDER_DATE TIMESTAMP(6),
PRIMARY KEY (ORDER_ID)
);
CREATE TABLE ORDER_B
(
ORDER_ID NUMBER NOT NULL,
ORDER_CODE VARCHAR2(50),
ORDER_STATUS VARCHAR2(20),
ORDER_USER_ID NUMBER,
ORDER_DATE TIMESTAMP(6)
INSERT_AT TIMESTAMP(6),
UPDATED_AT TIMESTAMP(6),
PRIMARY KEY (ORDER_ID)
);
I have no idea how to do this and what is the best way (with a trigger, procedure, using merge, etc.)
Can someone give me a direction, please?
Here is some pseudo-code to show you a potential trigger based solution that does not fall back into slow row-by-row processing.
create or replace trigger mytrg
for insert or update delete on ordera
compound trigger
pklist sys.odcinumberlist;
before statement is
begin
pklist := sys.odcinumberlist();
end before statement ;
after each row is
begin
pklist.extend;
pklist(pklist.count) := :new.order_id;
end before each row;
after statement is
begin
merge into orderb b
using (
select a.*
from ordera a,
table(pklist) t
where a.order_id = t.column_value
) m
when matched then
update
set b.order_code = m.order_code,
b.order_status = m.order_status,
...
where decode(b.order_code,m.order_code,0,1)=1
or decode(b.order_status,m.order_status,0,1)=1
....
when not matched then
insert (b.order_id,b.order_code,....)
values (m.order_id,m.order_code,....);
end after statement ;
end;
We hold the impacted primary keys, and then build a single merge later, with an WHERE embed to minimise update activities.
If your application allows the update of primary keys, you'd need some additions, but this should get you started

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.

Oracle - Using a trigger to delete a row

When an insert is made to Condo_assign I am using multiple triggers to add entries to a reserveError table to indicate what the error was. After they are logged in reserveError I am now attempting to use another trigger to delete the record from condo_assign that caused the problem. Basically, the erroneous insert should be logged in ReserveError and Deleted from Condo_assign. The problem is that while my delete trigger compiles and causes no problems, it doesn't appear to do anything. when I select * from condo_assign the erroneous entries are still there.
Condo_Assign table:
CREATE TABLE Condo_Assign (
MID INT
, RID VARCHAR2(3)
, CONSTRAINT Condo_Assign Primary Key (MID,RID)
, CONSTRAINT MID_Assign_FK Foreign Key (MID) references SkiClub (MID)
, CONSTRAINT RID_Assign_FK Foreign Key (RID) references Condo_Reservation (RID)
);
reserveError Table:
CREATE TABLE ReserveError (
Err INT PRIMARY KEY
, MID INT
, RID VARCHAR2(3)
, errorDate DATE
, errorCode VARCHAR2(6)
, errorMsg VARCHAR2(60)
, CONSTRAINT Error_MID_FK FOREIGN KEY (MID) REFERENCES SkiClub
, CONSTRAINT Error_RID_FK FOREIGN KEY (RID) REFERENCES Condo_Reservation
);
Procedure that causes trigger to fire:
CREATE OR REPLACE Procedure addCondo_Assign
(
inMID in Condo_Assign.MID%type
, inRID in Condo_Assign.RID%type
, inPaymentDate in Payment.PaymentDate%type
, inPayment in Payment.Payment%type
)
is
begin
insert into Condo_Assign (MID,RID) values (inMid,inRid);
IF inPayment >= 50 then
insert into Payment (MID,RID,PaymentDate,Payment) values (inMID,inRID,inPaymentDate,inPayment);
ELSE
raise_application_error(-20088,'Deposit less than 50');
end if;
exception
when others then
raise_application_error(-20005,'Cannot add to entry to Condo_Assign Table.');
end addCondo_Assign;
/
Trigger that writes to ReserveError table
-- Trigger to prevent gender mismatchs in room assignment
CREATE OR REPLACE TRIGGER Gender_Assign_Trigger
BEFORE INSERT ON Condo_Assign
FOR EACH ROW
DECLARE
Room_Gender Char(1);
Guest_Gender Char(1);
BEGIN
SELECT Gender
INTO Room_Gender
From Condo_Reservation
WHERE RID = :new.RID;
SELECT Gender
INTO Guest_Gender
FROM SkiClub
WHERE MID = :new.MID;
IF Room_Gender = 'M' AND Guest_Gender = 'F' THEN
addReserveError(:new.MID,:new.RID,SYSDATE,'g00001','Female guest assigned to male room');
ELSIF Room_Gender = 'F' AND Guest_Gender = 'M' THEN
addReserveError(:new.MID,:new.RID,SYSDATE,'g00002','Male guest assigned to female room');
END IF;
END Gender_Assign_Trigger;
/
Trigger that should delete entry from condo_assign:
CREATE OR REPLACE TRIGGER Remove_errors_trigger
after Insert on ReserveError
FOR EACH ROW
BEGIN
DELETE FROM Condo_Assign
WHERE MID = :new.MID and RID = :new.RID;
END remove_errors_trigger;
/
The entire process is started in a before insert trigger.
The code that tries to delete the record won't delete anything because the record has not been inserted yet. There is nothing to delete at the time the delete code is being fired.
Usually checks in triggers prevent the insertion of incorrect data throwing an exception. The code inserting the data will have to handle the exception.
But you already have a procedure that handles the insert. It even performs a check on the deposit. Why not handle the gender check there?

ORACLE SQL- After delete trigger on parent table with cascade set null, mutating error workaround

I am trying to update columns in my child table service after delete on parent table cars using function choose_ideal_car which uses some selects over cars and service tables.
Here is part of my sql script:
CREATE TABLE cars(car_id INT CONSTRAINT pk_id_cars);
CREATE TABLE service(
service_id INT CONSTRAINT pk_id_service PRIMARY KEY,
car_id INT CONSTRAINT fk_id_car REFERENCES cars(car_id) ON DELETE SET NULL,
period VARCHAR2(5) CONSTRAINT check_period CHECK period IN ('even','odd','every'),
period VARCHAR2(3) CONSTRAINT check_day CHECK day IN ('mon','tue','wed','thu','fri')
);
CREATE OR REPLACE TRIGGER service_set_car_id AFTER DELETE ON cars
DECLARE
CURSOR touched_rows is select * from service where car_id = null;
touched_row service%ROWTYPE;
BEGIN
OPEN touched_rows;
LOOP
FETCH touched_rows INTO touched_row;
EXIT WHEN touched_rows%NOTFOUND;
UPDATE service SET car_id = choose_ideal_car(touched_row.period,touched_row.day) WHERE service_id = touched_row.service_id;
END LOOP;
CLOSE touched_rows;
end;
/
For some reason my trigger is never fired.
I also tried creating triggers like this:
CREATE OR REPLACE TRIGGER service_set_car_id AFTER DELETE FROM cars FOR EACH ROW
CURSOR touched_rows is select * from service where car_id = :old.car_id;
...
Which is fired but throws 'mutating error' because function choose_ideal_car uses selects from both tables. Maybe the solution of this is to create duplicate of my cars table and select from it in my choose_ideal_car function instead of selecting from the cars table on which is my trigger defined, but that does not sounds good to me.
While I am writing this post I realized that even if my first trigger is fired it would not work correctly and throw the same 'mutating error'.
So in the end I have two questions:
1) Why is the first trigger never fired?
2) How to fix this mutating error and get all working correctly?
where car_id = null returns no rows, use where car_id is null instead