Cursors in oracle PL/SQL - oracle

I am trying to do a trigger that updates a status of a grade on update of the grade or a insert of a new one.
Thats my table
create table Enrollment (
Student_ID char(9) not null,
Course_ID char(5) not null,
Registered_Date date,
Grade NUMBER,
Status varchar(4),
constraint pkEnrollment primary key (Student_ID, Course_ID));
That's my code so far and i don't know what's the problem
create or replace TRIGGER PASS_FAIL
AFTER INSERT OR UPDATE OF GRADE ON ENROLLMENT
DECLARE
CURSOR change_grade IS
select GRADE, STATUS from ENROLLMENT FOR UPDATE;
newgrade ENROLLMENT.GRADE%type;
newstatus ENROLLMENT.STATUS%type;
BEGIN
OPEN change_grade;
LOOP
FETCH change_grade into newgrade,newstatus;
IF newgrade>=60 THEN UPDATE ENROLLMENT SET STATUS = 'Pass' WHERE CURRENT OF change_grade;
ELSE UPDATE ENROLLMENT SET STATUS = 'Fail' WHERE CURRENT OF change_grade;
END IF;
EXIT WHEN change_grade%NOTFOUND;
END LOOP;
CLOSE change_grade;
END;
When i tried to change the grade i get
UPDATE "FELIX"."ENROLLMENT" SET GRADE = '10' WHERE ROWID = 'AAAGETAABAAALKJAAE' AND ORA_ROWSCN = '3332070'
ORA-04098: trigger 'FELIX.ELIGIBLE_ENROLLMENT' is invalid and failed re-validation
One error saving changes to table "FELIX"."ENROLLMENT":
Row 5: ORA-04098: trigger 'FELIX.ELIGIBLE_ENROLLMENT' is invalid and failed re-validation
Thanks in advance for the help

Your code seems excessively complicated unless the point is to learn about cursors.
CREATE OR REPLACE TRIGGER PASS_FAIL
Before INSERT OR UPDATE
ON enrollment
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
DECLARE
/******************************************************************************
NAME: PASS_FAIL
PURPOSE:
USED BY:
REVISIONS:
Ver Date Author Description
--------- ---------- --------------- ------------------------------------
1.0 1. Created this trigger.
NOTES:
******************************************************************************/
BEGIN
if :new.grade > 60 THEN
:new.status := 'PASS';
ELSE
:new.status := 'FAIL';
END IF;
EXCEPTION
WHEN OTHERS
THEN
-- Log the error and then re-raise
RAISE;
END PASS_FAIL;

Related

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.

SQL Update Trigger not working as expected

I have an update trigger on my oracle table..but seems like it's not working..Schema under which triggers and table created is HR
CREATE OR REPLACE Trigger TR_FinlStatAssetDesignation_U
BEFORE update
on FINLSTATASSETDESIGNATION FOR EACH ROW
DECLARE
v_AtDateTime TIMESTAMP(3);
v_LogOperation NUMBER(3,0);
v_UserName VARCHAR2(255);
v_AppName VARCHAR2(255);
SWV_error NUMBER(10,0) DEFAULT 0;
BEGIN
begin
select USERNAME INTO v_UserName FROM v$session WHERE (audsid = SYS_CONTEXT('userenv','sessionid')) AND ROWNUM <=1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
end;
SELECT program INTO v_AppName FROM v$session WHERE audsid=userenv('sessionid');
if (LENGTH(v_AppName) = 0) then
v_AppName := 'Unknown';
end if;
SELECT SYSTIMESTAMP INTO v_AtDateTime FROM dual;
if UPDATING('FinlStatAssetDesignation') then
RAISE_APPLICATION_ERROR(-20000,'Invalid attempt to update OID FinlStatAssetDesignation in FinlStatAssetDesignation');
/*
ROLLBACK */
return;
end if;
if not UPDATING('UpdDate') then
SWV_error := 0;
begin
UPDATE FinlStatAssetDesignation a SET(UpdDate) =(SELECT distinct v_AtDateTime FROM dual where a.FinlStatAssetDesignation = :NEW.FinlStatAssetDesignation)
WHERE ROWID IN(SELECT a.ROWID FROM FinlStatAssetDesignation a where a.FinlStatAssetDesignation = :NEW.FinlStatAssetDesignation);
EXCEPTION
WHEN OTHERS THEN
SWV_error := SQLCODE;
end;
if SWV_error <> 0 then
/*
ROLLBACK */
return;
In this trigger.. second part i.e below is not working of trigger..please help....it is not updating the timestamp in upddate column
SQL> select * from finlstatassetdesignation;
FINLSTATAS FINLSTATASSETDESIGNATIONDESC UPDOPERATION
---------- -------------------------------------------------- ------------
UPDDATE
---------------------------------------------------------------------------
one19 anyt 0
hinh
01-JAN-17 08.00.00.000000 AM
SQL> update finlstatassetdesignation set finlstatassetdesignationdesc ='nothing';
1 row updated.
SQL> select * From finlstatassetdesignation;
FINLSTATAS FINLSTATASSETDESIGNATIONDESC UPDOPERATION
---------- -------------------------------------------------- ------------
UPDDATE
---------------------------------------------------------------------------
one19 nothing 0
01-JAN-17 08.00.00.000000 AM
What are you trying to accomplish in the ON INSERT trigger? From your comment of
I still can insert a row as user "hello"..hence trigger is not working..
you seem to want to prevent a user named HELLO from being added - but in your trigger code if a username of HELLO is found you simply return from the trigger. That doesn't accomplish anything. To signal to the system that you don't want the INSERT to proceed you must raise an exception in your trigger. For example:
CREATE OR REPLACE Trigger TR_FinlStatAssetDesignation_I
BEFORE Insert on FINLSTATASSETDESIGNATION
BEGIN
if USER = 'HELLO' or USER = 'SYSTEM' then
RAISE_APPLICATION_ERROR(-20000, 'Invalid user in TR_FinlStatAssetDesignation_I');
end if;
END TR_FinlStatAssetDesignation_I;
Raising an exception in this manner will prevent the INSERT from proceeding to successful completion. Your code is responsible for providing an appropriate error handler.
Best of luck.

Triggers in sql: Mutating Trigger issue

I want to insert rows into another table called excercise_scores when a row is inserted in attempts table. There are multiple tries for an excercise and based on the scoring method(which can be latest score, average of all attempts or maximum score from all attempts), I have to insert score in the excercise_scores table.
So when Inserting scores into excercise_score, I also have to check what is the grading method for the excercise, get the score from attempts table based on the method and then finally insert into excercise_scores table.
For that I have created a SQL trigger as follows
CREATE OR REPLACE TRIGGER scores_trigger AFTER
INSERT
ON attempts FOR EACH row DECLARE P1 VARCHAR2(50);
SCORES FLOAT;
v_exists VARCHAR2(1) := 'F';
BEGIN
BEGIN
SELECT
'T'
INTO
v_exists
FROM
excercise_scores
WHERE
user_id = :NEW.USER_ID
AND EXCERCISE_ID = :NEW.EXCERCISE_ID
AND COURSE_ID = :NEW.COURSE_ID;
EXCEPTION
WHEN no_data_found THEN
NULL;
END;
SELECT
SELECTION_METHOD
INTO
P1
FROM
EXCERCISES
WHERE
EXCERCISE_ID = :NEW.EXCERCISE_ID
AND COURSE_ID = :NEW.COURSE_ID;
IF (P1 = 'Latest') THEN
SCORES := :NEW.TOTAL_POINTS;
ELSE
IF (P1 = 'best') THEN
SELECT
MAX(TOTAL_POINTS)
INTO
SCORES
FROM
ATTEMPTS
WHERE
EXCERCISE_ID = :NEW.EXCERCISE_ID
AND COURSE_ID = :NEW.COURSE_ID
AND USER_ID = :NEW.USER_ID;
ELSE
SELECT
AVG(TOTAL_POINTS)
INTO
SCORES
FROM
ATTEMPTS
WHERE
EXCERCISE_ID = :NEW.EXCERCISE_ID
AND COURSE_ID = :NEW.COURSE_ID
AND USER_ID = :NEW.USER_ID;
END IF;
END IF;
IF v_exists = 'T' THEN
UPDATE
EXCERCISE_SCORES
SET
TOTAL_POINTS = SCORES
WHERE
user_id = :NEW.USER_ID
AND EXCERCISE_ID = :NEW.EXCERCISE_ID
AND COURSE_ID = :NEW.COURSE_ID;
ELSE
INSERT
INTO
EXCERCISE_SCORES VALUES
(
:NEW.EXCERCISE_ID,
:NEW.COURSE_ID,
:NEW.COURSE_ID,
SCORES
) ;
END IF;
END;
But when I try to execute this trigger, it gives me the following error:
Error starting at line 47 in command:
insert into attempts values(21,0,'vshesha',3,'CSC540',TO_DATE('20141014', 'YYYYMMDD'),12,6)
Error report:
SQL Error: ORA-04091: table VSHESHA.ATTEMPTS is mutating, trigger/function may not see it
ORA-06512: at "VSHESHA.SCORES_TRIGGER", line 18
ORA-04088: error during execution of trigger 'VSHESHA.SCORES_TRIGGER'
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
I couldn't figure out why it is giving this error as I am running the trigger after insert. Generally mutating table issue should not come in after insert triggers.
Can anyone please help me with this. I am suck with this for a long time now.
The error message is pretty self-explaining, and the SQL statement that's causing it is:
SELECT
AVG(TOTAL_POINTS)
INTO
SCORES
FROM
ATTEMPTS
WHERE
EXCERCISE_ID = :NEW.EXCERCISE_ID
AND COURSE_ID = :NEW.COURSE_ID
AND USER_ID = :NEW.USER_ID;
You're selecting from ATTEMPTS, and that's the table that's currently changing. There's no simple solution for it - the most sensible approach is to use move your business logic out of the trigger and into your application code (see AskTom about mutating table errors ).
BTW: Your statement that "Generally mutating table issue should not come in after insert triggers." is plain wrong. See database journal article for a longer explanation (in short: you will always run into mutating table errors with multi-row inserts, and in almost every case run into errors with single-row inserts).
In Oralce you can use COMPOUND TRIGGER. In your case it would be similar to this one. I did not copy all your operations to this example, but you should get the principle.
However, as the other replies already mentionied. The preferred way should be to move all this logic into a PL/SQL procedure.
CREATE OR REPLACE TRIGGER scores_trigger
FOR INSERT ON attempts
COMPOUND TRIGGER
TYPE RowTableType IS TABLE OF ROWID;
RowTable RowTableType;
aRow attempts%ROWTYPE;
SCORES FLOAT;
v_exists VARCHAR2(1) := 'F';
BEFORE STATEMENT IS
BEGIN
RowTable := RowTableType();
END BEFORE STATEMENT;
AFTER EACH ROW IS
BEGIN
RowTable.EXTEND;
RowTable(RowTable.LAST) := :NEW.ROWID;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN RowTable.FIRST..RowTable.LAST LOOP
SELECT *
INTO aRow
FROM attempts
WHERE ROWID = RowTable(i);
SELECT SELECTION_METHOD
INTO P1
FROM EXCERCISES
WHERE EXCERCISE_ID = aRow.EXCERCISE_ID
AND COURSE_ID = aRow.COURSE_ID;
IF P1 = 'Latest' THEN
SCORES := :NEW.TOTAL_POINTS;
ELSE
IF (P1 = 'best') THEN
SELECT MAX(TOTAL_POINTS)
INTO SCORES
FROM ATTEMPTS
WHERE EXCERCISE_ID = aRow.EXCERCISE_ID
AND COURSE_ID = aRow.COURSE_ID
AND USER_ID = aRow.USER_ID;
ELSE
SELECT AVG(TOTAL_POINTS)
INTO SCORES
FROM ATTEMPTS
WHERE EXCERCISE_ID = aRow.EXCERCISE_ID
AND COURSE_ID = aRow.COURSE_ID
AND USER_ID = aRow.USER_ID;
END IF;
END IF;
END LOOP;
END AFTER STATEMENT;
END scores_trigger;
An example for an PL/SQL procedure would be this one:
CREATE OR REPLACE PROCEDURE insert_score(V_EXCERCISE IN SCORES.EXCERCISE_ID%TYPE, V_COURSE IN SCORES.COURSE_ID%TYPE, V_USER IN USER_ID%TYPE) IS
BEGIN
INSERT INTO insert_score (EXCERCISE_ID, COURSE_ID, USER_ID) VALUES (V_EXCERCISE, V_COURSE, V_USER);
BEGIN
SELECT 'T'
INTO v_exists
FROM excercise_scores
WHERE user_id = :NEW.USER_ID
AND EXCERCISE_ID = :NEW.EXCERCISE_ID
AND COURSE_ID = :NEW.COURSE_ID;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN;
END;
... other stuff
END insert_score;
For the insert you have to call this procedure instead of doing the insert directly.

Oracle trigger checks previous record before insert/update

I would like to create a trigger that prevents a student from enrolling into a new module if he has any outstanding bills.
studID studNRIC paymentStatus
-------------------------------------
200 F7654672F Non Payment
it would reject the following statement:
INSERT INTO student(studID, studNRIC, paymentStatus)
VALUES (201, 'F7654672F', 'Good');
I've came out with the following trigger but I'm still able to insert a new student.
set define off;
CREATE OR REPLACE TRIGGER reject_new_account
AFTER INSERT OR UPDATE ON Student
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
totover NUMBER(3);
BEGIN
SELECT COUNT (*)
INTO totover
FROM Student
WHERE :NEW.nric = student.nric
AND :NEW.paymentStatus = 'Non Payment';
IF totover > 0 THEN
RAISE_APPLICATION_ERROR ( -20002,
'Student' || :NEW.nric ||
' has outstanding bills' );
END IF;
END;
/
there's seems to be a problem with line 13 AND :NEW.paymentStatus = 'Non Payment';
so how do I go about doing this?
table structure
CREATE TABLE Student(
studID INTEGER NOT NULL,
firstName CHAR(25) NULL,
lastName CHAR(25) NULL,
NRIC CHAR(9) NOT NULL,
paymentStatus CHAR(25) Default 'Good',
CONSTRAINT stud_Pkey PRIMARY KEY (studID),
CONSTRAINT studPaymentStatus_type CHECK (PaymentStatus IN ('Late Payment', 'Non Payment', 'Good'))
);
There are a couple of issues with this trigger.
First if you want to prevent an INSERT you need to use a BEFORE trigger. An AFTER trigger will fire after the insert has successfully completed, by which point it is too late to stop the insert.
Secondly, I'm unsure about what you are trying to achieve with your SQL statement. Since the trigger is attached to the customer table you don't need to do a select on the customer table to access the record data. You can just do:
IF :NEW.badstatus = 'Non Payment'
THEN
RAISE_APPLICATION_ERROR ( -20002,
'Employee ' || :NEW.nric ||
' already has 5 readings' );
END IF;
You probably don't want to check if the new record has a bad status, you want to check if the existing customer records have a bad status. As James mentioned, a BEFORE trigger probably makes more sense, but that depends on what you are trying to do, whether the insert gets rollback or not by the caller, etc.
CREATE OR REPLACE TRIGGER reject_new_account
AFTER INSERT OR UPDATE ON Customer
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
totover NUMBER(3);
BEGIN
SELECT COUNT (*)
INTO totover
FROM Customer c
WHERE c.nric = :NEW.nric
AND c.badstatus = 'Non Payment';
IF totover > 0 THEN
RAISE_APPLICATION_ERROR ( -20002,
'Employee ' || :NEW.nric ||
' already has 5 readings' );
END IF;
END;
/
I've removed the PRAGMA AUTONOMOUS_TRANSACTION; for you and replace it with an exception to handle the issue, can you try if it works.
set define off;
CREATE OR REPLACE TRIGGER reject_new_account
BEFORE INSERT OR UPDATE ON CUSTOMER
FOR EACH ROW
DECLARE
totover CHAR(100);
BEGIN
SELECT distinct badStatus
INTO totover
FROM customer
WHERE :NEW.nric = CUSTOMER.nric;
IF totover = 'Non Payment' THEN
RAISE_APPLICATION_ERROR ( -20003,
'Customer ' || :NEW.firstName||''||:NEW.lastName ||
' cannot create new account due to bad payment' );
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
END;
/
As I assume you have discovered, you cannot select from the same table that a row-level trigger is defined against; it causes a table mutating exception. You have attempted to get round this by adding the autonomous transaction pragma. Unfortunately, although this works, it is just covering up your mistake in methodology.
In order to properly create this validation using a trigger a procedure should be created to obtain user-specified locks so the validation can be correctly serialized in a multi-user environment.
PROCEDURE request_lock
(p_lockname IN VARCHAR2
,p_lockmode IN INTEGER DEFAULT dbms_lock.x_mode
,p_timeout IN INTEGER DEFAULT 60
,p_release_on_commit IN BOOLEAN DEFAULT TRUE
,p_expiration_secs IN INTEGER DEFAULT 600)
IS
-- dbms_lock.allocate_unique issues implicit commit, so place in its own
-- transaction so it does not affect the caller
PRAGMA AUTONOMOUS_TRANSACTION;
l_lockhandle VARCHAR2(128);
l_return NUMBER;
BEGIN
dbms_lock.allocate_unique
(lockname => p_lockname
,lockhandle => p_lockhandle
,expiration_secs => p_expiration_secs);
l_return := dbms_lock.request
(lockhandle => l_lockhandle
,lockmode => p_lockmode
,timeout => p_timeout
,release_on_commit => p_release_on_commit);
IF (l_return = 1) THEN
raise_application_error(-20001, 'dbms_lock.request Timeout');
ELSIF (l_return = 2) THEN
raise_application_error(-20001, 'dbms_lock.request Deadlock');
ELSIF (l_return = 3) THEN
raise_application_error(-20001, 'dbms_lock.request Parameter Error');
ELSIF (l_return = 5) THEN
raise_application_error(-20001, 'dbms_lock.request Illegal Lock Handle');
ELSIF (l_return not in (0,4)) THEN
raise_application_error(-20001, 'dbms_lock.request Unknown Return Value ' || l_return);
END IF;
-- Must COMMIT an autonomous transaction
COMMIT;
END request_lock;
This procedure can then be used in a compound trigger (assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions)
CREATE OR REPLACE TRIGGER reject_new_account
FOR INSERT OR UPDATE ON student
COMPOUND TRIGGER
-- Table to hold identifiers of inserted/updated students
g_studIDs sys.odcinumberlist;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal student table
g_studIDs := g_studIDs();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the inserted/updated students; the payment status may be updated
-- without checking the constraint
IF ( INSERTING
OR ( UPDATING
AND ( :new.studID <> :old.studID
OR :new.NRIC <> :old.NRIC)))
THEN
g_studIDs.EXTEND;
g_studIDs(g_studIDs.LAST) := :new.studID;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_students
IS
SELECT sdt.studID
, sdt.NRIC
FROM TABLE(g_studIDs) sid
INNER JOIN student sdt
ON (sdt.studID = sid.column_value)
ORDER BY sdt.NRIC;
CURSOR csr_constraint_violations
(p_studID student.studID%TYPE
,p_NRIC student.studNRIC%TYPE)
IS
SELECT NULL
FROM student sdt
WHERE sdt.NRIC = p_NRIC
AND sdt.paymentStatus = 'Bad Payment'
AND sdt.studID <> p_studID;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated student there exists another record for
-- the same NRIC with a Bad Payment status. Serialise the constraint for each
-- NRIC so concurrent transactions do not affect each other
FOR r_student IN csr_students LOOP
request_lock('REJECT_NEW_ACCOUNT_' || r_student.NRIC);
OPEN csr_constraint_violations(r_student.studID, r_student.NRIC);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Student ' || r_student.NRIC || ' has Bad Payment status');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;

How to create a delete stored procedure making sure it cannot be deleted when it has affiliated records in another table?

I have tables
supplier( supplier_id (pk), name, address) supplier_invoice( supp_invoice-id(pk), balance)
supplier_product (supp_prod_id(pk), Supplier_id(fk), product_id(fk), supp_invoice_id(fk))
I am trying to execute a delete procedure:
set serveroutput on;
create or replace
procedure delete_supp
(d_supplier_id int)
is
v_count int;
begin
select count(*) into v_count from supplier_product where d_supplier_id=supplier_id;
if v_count > 0 then
dbms_output.put_line('Supplier cannot be deleted because there is an existing invoice in the system');
else
begin
delete from supplier where supplier_id=d_supplier_id;
DBMS_OUTPUT.PUT_LINE('Deleted ' || SQL%ROWCOUNT || ' Rows.');
commit;
end;
end if;
Exception
when others then
dbms_output.put_line('Delete failed');
end;
/
This procedure works but rather than checking if the record exists in supplier_product, I want to make sure a supplier cannot be deleted if he has open invoice in the supplier_invoice table. I tried with looping in supplier_invoice table but couldn't make it to work.
Small change to your delete statement
DELETE FROM supplier
WHERE supplier_id = d_supplier_id
AND NOT EXISTS (SELECT 1
FROM supplier_product,
supplier_invoice
WHERE supp_invoice_id = supp_invoice_id
AND supplier_id = d_supplier_id);
IF SQL%ROWCOUNT = 0 THEN
RAISE Invoice_exists_exception;
END IF;
and this will ensure that supplier records will be deleted only if no records with the same supplier_id & the linked invoice id exists in the supplier_product table.
Of course, if the FKs are set up (as you mention) - then it should raise an exception when you're trying to delete..
Update: I used SQLFiddle to build a sample schema/data to show this:
If you have Foreign keys set up, you'll run into a ORA-02292: integrity constraint violated exception which you can trap & handle appropriately.

Resources