Problem with trigger for avoiding insert (Oracle SQL) - sql

I'm trying to make a trigger to avoid inserting data in the table for this reason:
If a patient is dead he can't make a visit.
I made a function that return a 1 if the patient is dead and I call it on the trigger. That's the code:
CREATE OR REPLACE FUNCTION check_d (pd INTEGER)
RETURN INTEGER
IS
d Patient.d_date%TYPE;
BEGIN
SELECT P.d_Date INTO d
FROM Patient P
WHERE P.idP=pd;
IF d <> NULL THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
CREATE OR REPLACE TRIGGER bad_insert
BEFORE INSERT ON Visit
FOR EACH ROW
DECLARE
idd INTEGER;
dp Visit.id_PV%TYPE;
BEGIN
SELECT V.id_PV INTO dp
FROM Visit V
WHERE id_PV=:NEW.id_PV;
idd := check_d(dp);
IF idd = 1 THEN
RAISE_APPLICATION_ERROR(-20014,'Error. Patient Deceased.');
END IF;
END;
/
The function should be ok, the problem is in thet trigger.
Visit.id_PV is a Foreign Key to Patient.idP(PRIMARY KEY) anyway. How should I modify the code? Thanks.

Your function is not correct
d <> NULL
Will always return UNKNOWN, instead you have to check for nulls like:
d is not null
Once you've fixed that, you still have to work around the mutating table error you'd receive when you try an insert statement (did you not receive this when you tried it?). You are referencing the same table you are inserting into within your trigger. You can't do that. Instead you should reference the column you want using :new.id_PV .

I think the problem is located here :
IF d <> NULL THEN
Change it to :
IF d IS NOT NULL THEN

In the current case, you'll get no_data_found exception because of the query within the trigger. Moreover, you do not need to return the value for id_PV column that you already have as :NEW.id_PV, and such attempt mostly end with mutating trigger error. So , rearrange your trigger as
CREATE OR REPLACE TRIGGER bad_insert
BEFORE INSERT ON Visit
FOR EACH ROW
BEGIN
IF check_d(:NEW.id_PV) = 1 THEN
RAISE_APPLICATION_ERROR(-20014, 'Error. Patient Deceased.');
END IF;
END;
/
while getting rid of redundant local variables.
The function might also be made brief such as
CREATE OR REPLACE FUNCTION check_d (pd Visit.id_PV%type) RETURN INT IS
val INT;
BEGIN
SELECT NVL(SUM(NVL2(P.d_Date,1,0)),0)
INTO val
FROM Patient P
WHERE P.idP=pd;
RETURN val;
END;
/
Demo

Related

trigger insert only if tupel satisfies condition

I want to create a trigger which checks before insert if the tupel which is supposed to be inserted holds a specific condition (which also depends on another table).
For example:
create trigger or replace check_tupel
before insert on A
for each row
execute
if exists (select x,y from B where B.x = A.x and B.y = A.y)
Oh I am using postgreSQL 13.
EDIT: Yes I know that I can do this without a trigger, but I am asking for a solution with a trigger for a reason.
I hope there is a way to do this... My other idea was to create a UDF which gets called before insert but I do not know how to check the condition in this UDF and only insert if the function returns true.
If you simply wanna automatically validate a record before inserting it on table A based on table B using a User Defined Function, you do not need a trigger at all. Consider adding a simple CHECK CONSTRAINT:
CREATE TABLE a (
x int,
y int,
CONSTRAINT exists_in_b CHECK (NOT myfunc(x,y))
);
Demo: db<>fiddle
CREATE TABLE b (x int,
y int);
INSERT INTO b VALUES (42,42);
CREATE OR REPLACE FUNCTION myfunc(x int, y int)
RETURNS BOOLEAN AS $BODY$
SELECT EXISTS (SELECT 1 FROM b WHERE b.y =$1 AND b.x=$2 )
$BODY$
LANGUAGE sql;
CREATE TABLE a (
x int,
y int,
CONSTRAINT exists_in_b CHECK (NOT myfunc(x,y)) -- here the magic happens
);
Now, if we try to insert a value that our function does not validate, it raises an exception:
INSERT INTO a VALUES (42,42);
ERROR: new row for relation "a" violates check constraint "exists_in_b"
DETAIL: Failing row contains (42, 42).
SQL state: 23514
EDIT (See comments): Solution using a trigger
CREATE OR REPLACE FUNCTION myfunc()
RETURNS TRIGGER AS $BODY$
BEGIN
IF EXISTS (SELECT 1 FROM b WHERE b.y =NEW.y AND b.x=NEW.x ) THEN
RAISE EXCEPTION 'tuple already exists in "b": % %', NEW.x,NEW.y;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER check_tupel
BEFORE INSERT OR UPDATE ON a
FOR EACH ROW EXECUTE PROCEDURE myfunc();
Demo: db<>fiddle
So you need a trigger solution, sounds like a homework problem. The question then becomes do you want to:
abort the entire operation
slightly squash the row but continue the remainder of the operation.
The following does the second: (See demo)
create or replace
function check_b_has_a()
returns trigger
language plpgsql
as $$
begin
if exists
( select null
from b
where (b.x,b.y) =
(new.x, new.y)
)
then
return null;
else
return new;
end if;
end;
$$;
create trigger a_bir
before insert
on a
for each row
execute function check_b_has_a();

"If" in a trigger comparing two columns from 2 different tables- error

Im trying to create a trigger when updating table 'test' to make sure a value in a column is not greater than another one from a different table. But I get this error on Oracle Apex: ORA-24344: success with compilation error
'test' is a table and 'chestionar' is a second one, so I want to launch that error when I insert a value in 'punctaj' which is greater than the 'punctaj_max'. And the id of the both tables must be the same . What should I modify?
here is my code:
CREATE OR REPLACE trigger trg_a
BEFORE UPDATE on test
begin
if test.punctaj > chestionar.punctaj_max and test.id=chestionar.id then
raise_application_error(234,'error, the value is grater than maximum of that id');
end if;
end;
I think the logic you want is:
create or replace trigger trg_a
before update on test
for each row
declare
p_punctaj_max chestionar.punctaj_max%type;
begin
select punctaj_max into p_punctaj_max from chestionar c where c.id = :new.id;
if :new.punctaj > p_punctaj_max then
raise_application_error(234, 'error, the value is grater than maximum of that id');
end if;
end;
/
The idea is to recover the value of punctaj_max in table chestionar for the id of the row that is being updated in test (note that this implicitely assumes that there cannot be multiple matching rows in chestionar). We can then compare that to the value being updated, and raise the error if needed.
You have three (I think) issue in your code:
You should apply trigger at time of insert also.
you need to query the table chestionar and then compare the value.
the error number in raise_application_error should be negative and between -20999 and -20000.
So, I would rewrite your code as follows:
create or replace trigger trg_a
before update or insert on test
for each row
declare
lv_cnt number := 0;
begin
select count(1) into lv_cnt
from chestionar c
where c.id = :new.id
and :new.punctaj > c.punctaj_max;
if lv_cnt > 0 then
raise_application_error(-20234, 'error, the value is grater than maximum of that id');
end if;
end;
/

SQL - Count number of rows in a table before updating it

Im doing a college project on Oracle SQL and I keep getting the error "table (...) is mutating, trigger/function may not see it" when I try to update a table. My participateTeams table has 3 columns (eventName varchar(), teamId number, teamPlace number).
The Objective here is that when I update the teamPlace of a given team in an event, the place MUST be lower or equal than the number of participant teams in that same event.
My trigger is as follows:
Before update on participateTeams
for each row
Declare participants number;
Begin
select count(*) into participants from participateteams where :New.eventName = participateteams.eventName;
if(:NEW.teamplace>participants) then
RAISE_APPLICATION_ERROR(-20250,'O lugar da equipa é inválido');
end if;
End;
/
From what I've researched it's because I'm trying to read the same table that called the trigger.
I have also have tried exporting the select part of the code into a function, but the issue persists.
The participants in the event are the teams theirselves and not the individuals that form those same teams. Example: If team A, team B and team C participate in an event E, the count for that same event E should be 3.
Any tips? Thank you.
The mutating table error is just a pain. And what you are trying to do is rather tricky. Here is one approach:
Add a column to teams with the count of participants.
Maintain this column with insert/update, and delete triggers on participateteams.
Write a user-defined function to fetch the count for a given team.
Add a check constraint in participateteams using the user-defined function.
The logically correct trigger for what you want to achieve is this :-
CREATE OR REPLACE TRIGGER TRIG_CHK
AFTER INSERT OR UPDATE OF teamPlace ON participateteams
FOR EACH ROW
DECLARE
participants NUMBER;
BEGIN
select count(*) into participants from participateteams where :New.eventName =
participateteams.eventName;
if(:NEW.teamplace>participants) then
RAISE_APPLICATION_ERROR(-20250,'O lugar da equipa é inválido');
end if;
End;
update participateteams set teamPlace = 2 where eventName = 'B';
But when it gives Mutating table error when trying to update table participateteams,to solve this tricky situation , what I did is :-
step1 :Declare the table columns needed in a package header
CREATE OR REPLACE PACKAGE PKG_TEAMS AS
v_eventName participateteams.eventName%type;
v_teamPlace participateteams.teamPlace%type;
end;
step2 : Initialize the variables in a row level trigger
CREATE OR REPLACE TRIGGER TRG_row
AFTER INSERT OR UPDATE OF teamPlace
ON participateteams
FOR EACH ROW
BEGIN
PKG_TEAMS.v_eventName := :NEW.eventName;
PKG_TEAMS.v_teamPlace := :NEW.teamPlace;
END;
step3 :Now ,rather than using :NEW pseudo columns, using globally initialized variables
CREATE OR REPLACE TRIGGER TRG_stmt
AFTER INSERT OR UPDATE OF teamPlace ON participateteams
DECLARE
v_participants NUMBER;
BEGIN
SELECT COUNT(*) INTO v_participants FROM participateteams
WHERE eventName = PKG_TEAMS.v_eventName;
if(PKG_TEAMS.v_teamPlace>v_participants) then
RAISE_APPLICATION_ERROR(-20250,'CANNOT UPDATE,AGAINST RULES');
end if;
End;

Error(6,87): PLS-00103: Encountered the symbol "JOIN" when expecting one of the following/compound trigger

When I try to compile below compound trigger I got the error message. Please suggest what can be done to clear those error.
I tried to use normal trigger but its throwing ORA-04091 ERROR.
create or replace TRIGGER "WS5108"."AL_PROJECT_ORACLE_CODE_TRG" FOR
INSERT
ON ITIB_REQUESTS
COMPOUND TRIGGER
DECLARE
V_CODE varchar (200);
BEGIN
IF :NEW."J_PROJECT_ORACLE_CODE" IS NULL THEN
SELECT distinct (PROJECT_ORACLE_CODE) INTO V_CODE
FROM ITIB_PROJECT_ORACLE_CODE POC
JOIN ITIB_VPDOMAIN VP ON (POC.VP_DOMAIN = VP.VP_DOMAIN)
WHERE POC.VP_DOMAIN = (SELECT VP_DOMAIN
FROM ITIB_VPDOMAIN
WHERE ID = :NEW."VP_DOMAIN")
AND CAPEX_CATEGORY = :NEW."C_CAPEX_CATEGORY"
AND :NEW."C_TOTAL_EURO" <= 250 ;
END IF;
:NEW.J_PROJECT_ORACLE_CODE := V_CODE;
EXCEPTION
when no_data_found then
V_CODE := null ;
END AL_PROJECT_ORACLE_CODE_TRG;
I am trying to add value for one column in table after new line inserted in that table if that column is empty. This column value I am taking from that select condition and new values which are inserted in table.
The error in your question title is the second of two; running the reformatted code in your question I see:
LINE/COL ERROR
--------- -------------------------------------------------------------
2/1 PLS-00103: Encountered the symbol "DECLARE" when expecting one of the following: function pragma procedure subtype type <an identifier> <a double-quoted delimited-identifier> current cursor delete exists prior before after instead
8/9 PLS-00103: Encountered the symbol "JOIN" when expecting one of the following: , ; for group having intersect minus order start union where connect
(with different line number due to reformatting). In general you should fix the first error reported first, as subsequent errors are often unhelpful and caused by side-effects of the earlier ones. That is the case here. If you fix the compound trigger syntax to address the error reported against DECLARE then the JOIN is no longer a problem either:
create or replace TRIGGER "WS5108"."AL_PROJECT_ORACLE_CODE_TRG"
FOR INSERT
ON ITIB_REQUESTS
COMPOUND TRIGGER
BEFORE EACH ROW IS
V_CODE varchar (200);
BEGIN
IF :NEW."J_PROJECT_ORACLE_CODE" IS NULL THEN
SELECT distinct PROJECT_ORACLE_CODE INTO V_CODE
...
END IF;
:NEW.J_PROJECT_ORACLE_CODE := V_CODE;
EXCEPTION
when no_data_found then
V_CODE := null ;
END BEFORE EACH ROW;
END AL_PROJECT_ORACLE_CODE_TRG;
/
Basically, the main trigger body code you had needed to be in a BEFORE EACH ROW block, since it is a compound trigger.
Incidentally, you probably want V_CODE to be declared as varchar2 rather than varchar, or as ITIB_PROJECT_ORACLE_CODE.PROJECT_ORACLE_CODE%TYPE. If you need that variable at all; the assignment back to the :NEW field probably wants to be inside the IF block (as if that is not initially null, you set it to v_code, which is then null!), but you can also select straight into the :NEW variable. So I think you really want something more like:
create or replace TRIGGER WS5108.AL_PROJECT_ORACLE_CODE_TRG
FOR INSERT
ON ITIB_REQUESTS
COMPOUND TRIGGER
BEFORE EACH ROW IS
BEGIN
IF :NEW.J_PROJECT_ORACLE_CODE IS NULL THEN
SELECT PROJECT_ORACLE_CODE
INTO :NEW.J_PROJECT_ORACLE_CODE
FROM ITIB_PROJECT_ORACLE_CODE POC
JOIN ITIB_VPDOMAIN VP ON (POC.VP_DOMAIN = VP.VP_DOMAIN)
WHERE POC.VP_DOMAIN = (SELECT VP_DOMAIN
FROM ITIB_VPDOMAIN
WHERE ID = :NEW."VP_DOMAIN")
AND CAPEX_CATEGORY = :NEW.C_CAPEX_CATEGORY
AND :NEW.C_TOTAL_EURO <= 250 ;
END IF;
EXCEPTION
when no_data_found then
null ; -- do nothing; :NEW.J_PROJECT_ORACLE_CODE is already null
END BEFORE EACH ROW;
END AL_PROJECT_ORACLE_CODE_TRG;
/
I don't really see why this needs to be a compound trigger though, you aren't accessing the table the trigger is against (as you were in your previous question) and you only have a single scenario - only before insert; so a simple trigger would work here:
create or replace WS5108.TRIGGER AL_PROJECT_ORACLE_CODE_TRG
BEFORE INSERT
ON ITIB_REQUESTS
FOR EACH ROW
BEGIN
IF :NEW.J_PROJECT_ORACLE_CODE IS NULL THEN
SELECT PROJECT_ORACLE_CODE
INTO :NEW.J_PROJECT_ORACLE_CODE
FROM ITIB_PROJECT_ORACLE_CODE POC
JOIN ITIB_VPDOMAIN VP ON (POC.VP_DOMAIN = VP.VP_DOMAIN)
WHERE POC.VP_DOMAIN = (SELECT VP_DOMAIN
FROM ITIB_VPDOMAIN
WHERE ID = :NEW.VP_DOMAIN)
AND CAPEX_CATEGORY = :NEW.C_CAPEX_CATEGORY
AND :NEW.C_TOTAL_EURO <= 250 ;
END IF;
EXCEPTION
when no_data_found then
null ; -- do nothing; :NEW.J_PROJECT_ORACLE_CODE is already null
END AL_PROJECT_ORACLE_CODE_TRG;
/
And even the query within that looks confused; you don't need the subquery:
...
SELECT PROJECT_ORACLE_CODE
INTO :NEW.J_PROJECT_ORACLE_CODE
FROM ITIB_PROJECT_ORACLE_CODE POC
JOIN ITIB_VPDOMAIN VP ON (POC.VP_DOMAIN = VP.VP_DOMAIN)
WHERE VP.ID = :NEW.VP_DOMAIN
AND CAPEX_CATEGORY = :NEW.C_CAPEX_CATEGORY
AND :NEW.C_TOTAL_EURO <= 250 ;
...

Create Before Insert Trigger in oracle and implement in asp.net web form

I have a requirement of which I want to create a trigger in Oracle.
So what I want is: I have a table called XXCUS.XXACL_PN_PROSPECT_TRL in which there is a column called ACTION whose default value is set as RELEASE, EXTEND and CANCEL.
So my trigger would be on the table XXCUS.XXACL_PN_PROSPECT_TRL as
whenever the column ACTION has values as
RELEASE then the other table column should get UPDATED as R with the same matching ID
EXTEND then the other table column should get UPDATED as O with the same matching ID
CANCEL then the other table column should get UPDATED as C with the same matching ID
The other table name is xxcus.xxacl_pn_leases_all whose column needs to be updated. Also the column name is CLOSE_FLAG.
I m trying like below
CREATE [ OR REPLACE ] TRIGGER XXACL_PN_PROSPECT_T
BEFORE INSERT
ON XXCUS.XXACL_PN_PROSPECT_TRL
[FOR EACH ROW]
DECLARE
-- variable declaration
BEGIN
-- trigger code
Insert into XXCUS.XXACL_PN_PROSPECT_TRL -- values for ACTION here
then
update xxcus.xxacl_pn_leases_all if 'RELEASE' then 'R', if 'EXTEND' then 'O' where mkey = dynamic
EXCEPTION
WHEN ...
-- exception handling
END;
but it is not working as I am not champ in creating Triggers. Kindly help me with this
There are some obvious syntax errors
You don't need square brackets. Those are in the documentation to indicate optional parts.
Your insert/update statements are wrong, need to correct them.
It should look on the lines of :
CREATE OR REPLACE TRIGGER XXACL_PN_PROSPECT_T
BEFORE INSERT
ON XXCUS.XXACL_PN_PROSPECT_TRL
DECLARE
-- variable declaration
BEGIN
UPDATE xxcus.xxacl_pn_leases_all
SET CLOSE_FLAG = DECODE(:NEW.ACTION, 'RELEASE','R','EXTEND','O', 'CANCEL','C')
WHERE ID = :NEW.ID
END;
This trigger will update CLOSE_FLAG column of xxcus.xxacl_pn_leases_all based on what value comes in Action column of XXACL_PN_PROSPECT_TRL table.
While going through the links and documentation, I tried my self and figured out the solution.
There were many syntax errors in the starting. But I guess, I was the only one to apply the exact logic and took help of the syntax online.
So here it was my TRIGGER which I wrote and it worked perfectly.
CREATE OR REPLACE TRIGGER xxcus.xxacl_pn_prospect_trl_trg
AFTER INSERT OR UPDATE
ON xxcus.xxacl_pn_prospect_trl
FOR EACH ROW
DECLARE
sql_error VARCHAR (10000);
v_mkey NUMBER;
v_action VARCHAR (10);
BEGIN
v_action := :NEW.action;
IF (v_action = 'Extend')
THEN
UPDATE xxcus.xxacl_pn_leases_all
SET no_of_days = :NEW.current_action_days
WHERE lease_num = :NEW.lease_no;
ELSIF (v_action = 'Release')
THEN
UPDATE xxcus.xxacl_pn_leases_all
SET no_of_days = 0,
close_flag = 'R'
WHERE lease_num = :NEW.lease_no;
ELSE
UPDATE xxcus.xxacl_pn_leases_all
SET no_of_days = 0,
close_flag = 'C'
WHERE lease_num = :NEW.lease_no;
END IF;
EXCEPTION
WHEN OTHERS
THEN
sql_error := SQLERRM; END;/