Cannot to create trigger properly - sql

Oracle apex, PL_Sql.
This is my trigger:
CREATE OR REPLACE TRIGGER NoMoreThanOneHorse
BEFORE INSERT OR UPDATE OF Jockey_ID
ON Horses
FOR EACH ROW
DECLARE
NumOfHorsesForJockey NUMBER(4);
BEGIN
SELECT COUNT(*) INTO NumOfHorsesForJockey FROM Horses
WHERE Jockey_ID = :NEW.Jockey_ID AND Horse_ID <> :NEW.Horse_ID;
IF NumOfHorsesForJockey > 0
THEN RAISE_APPLICATION_ERROR (-20445, 'Нельзя закрепить за лошадью уже занятого жокея!');
END IF;
END NoMoreThanOneHorse;
but every time i try to create it i have this error :ORA-24344: success with compilation error ORA-06512. I suppose it's because bad syntax

I don't know what the compile-time error might be - it compiles fine for me. But at run-time you'll probably get an `ORA-04091 Table HORSES is mutating, trigger cannot see it" error. You can't fetch from HORSES in a ROW trigger which is defined on the HORSES table.
What you need to do is to change this to an AFTER STATEMENT trigger, which you do by leaving off the FOR EACH ROW in the trigger definition, and changing the trigger point from BEFORE to AFTER. The problem is that in a statement trigger you don't have access to the :NEW or :OLD data, but you don't really need it. A statement trigger is invoked only once for each statement which is executed. So your trigger should look something like:
CREATE OR REPLACE TRIGGER NoMoreThanOneHorse
AFTER INSERT OR UPDATE OF Jockey_ID
ON Horses
DECLARE
nMaxHorses NUMBER;
BEGIN
SELECT MAX(HORSE_COUNT)
INTO nMaxHorses
FROM (SELECT JOCKEY_ID, COUNT(*) AS HORSE_COUNT
FROM Horses
GROUP BY Jockey_ID);
IF nMaxHorses > 1 THEN
RAISE_APPLICATION_ERROR (-20445, 'Нельзя закрепить за лошадью уже занятого жокея!');
END IF;
END NoMoreThanOneHorse;
You really don't care which jockey has been assigned too many horses, only that there is a jockey who's been put on more than one horse.
db<>fiddle here

Related

How to successfully reference another table before insert with a trigger

I'm trying to create a trigger to validate if a new entry in the table registraties (registrations) contains a valid MNR (employee number) but I'm getting stuck on the part where I'm referencing the table medewerkers (employees).
Could someone help me out?
CREATE OR REPLACE TRIGGER t_MNRcontrole
BEFORE INSERT OR UPDATE
ON registraties
DECLARE
MNR_medewerkers number (SELECT MNR FROM MEDEWERKERS);
FOR EACH ROW
BEGIN
IF :new.MNR <> MNR_medewerkers
THEN raise_application_error(-20111, 'Medewerker niet herkend!');
END IF;
END;
Error message received is
ORA-24344: success with compilation error
The PL/SQL assignment operator is :=, or select x into y from z to populate from a SQL query.
FOR EACH ROW is part of the trigger spec, not the PL/SQL code.
If :new.mnr is not present in the parent table, you will get a no_data_found exception, not a mismatched variable.
It's good practice for error messages to include details of what failed.
In programming, we use indentation to indicate code structure.
A fixed version would be something like:
create or replace trigger trg_mnrcontrole
before insert or update on registraties
for each row
declare
mnr_medewerkers medewerkers.mnr%type;
begin
select mw.mnr into mnr_medewerkers
from medewerkers mw
where mw.mnr = :new.mnr;
exception
when no_data_found then
raise_application_error(-20111, 'Medewerker '||:new.mnr||' niet herkend!');
end;
However, we can implement this kind of check better using a foreign key constraint, for example:
alter table registraties add constraint registraties_mw_fk
foreign key (mnr) references medewerkers.mnr;
MNR_medewerkers number (SELECT MNR FROM MEDEWERKERS);
will always fail because its not a NUMBER, unless your table happens to only have one single entry and even then I am not sure PLSQL will allow it to pass.
The more standard case for this would be to first declare the number, then in the codeblock you do a SELECT INTO along with a WHERE clause where you make sure to only pick one specific row from the table. Then you can compare that number with the new one.
If however you are not trying to compare to one specific row, but are instead checking if the entry exists in that table.
BEGIN
SELECT 1
INTO m_variable
FROM table
WHERE MNR = :new.MNR;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
m_variable = 1;
WHEN OTHERS THEN
m_variable = 0;
END;
Declare the m_variable beforehand, and then check if its 0 then report the error.
The too_many_rows is in case there is more than one row in the table with this MNR, and the OTHERS is there for the NO_DATA_FOUND, but I use OTHERS to handle everything else that could happen but probably wont.
Btw this is a code block to be included within the main code block, so between your BEGIN and IF, then just change the IF to check if the variable is 0.

PL/SQL Oracle Mutant table-how to encounter this issue plz?

th request is to assure that in the city we have only one branch,
Banch (idBranch(PK), city) for update and insert
the idea I got is to create a temporary table having all the data of the first one so i can escape the ora errors this is the code I wrote, i'm facing the error PL/SQL: ORA-00903 and I'm not so sure if one trigger can do he job in this case since it should be working for inserting and updating. I need your advice please, thank you.
` CREATE TABLE TEMP_BRANCH AS SELECT NumBranch, CITY FROM BRANCH;
CREATE OR REPLACE TRIGGER checkUniqueBranchPerCity
BEFORE INSERT OR UPDATE ON BRANCH
REFERENCING
NEW AS nextLine
FOR EACH ROW
BEGIN
LOCK TABLE TEMP_BRANCH IN ROW SHARE MODE;
FOR line IN (SELECT * FROM TEMP_BRANCH) LOOP
IF :nextLine.City = line.City THEN
RAISE_APPLICATION_ERROR;
END IF;
END LOOP;
DELETE * FROM TEMP_BRANCH;
END;
/ `
ORA-00903 is "Invalid table name". I suspect it's caused because you put a colon in front of nextLine, i.e. you wrote
IF :nextLine.City = line.City THEN
when it should be
IF nextLine.City = line.City THEN
But I suggest getting rid of the REFERENCING clause and just using :NEW and :OLD in triggers as it reduces confusion. So use
CREATE TABLE TEMP_BRANCH AS SELECT NumBranch, CITY FROM BRANCH;
CREATE OR REPLACE TRIGGER checkUniqueBranchPerCity
BEFORE INSERT OR UPDATE ON BRANCH
FOR EACH ROW
BEGIN
LOCK TABLE TEMP_BRANCH IN ROW SHARE MODE;
FOR line IN (SELECT * FROM TEMP_BRANCH) LOOP
IF :NEW.City = line.City THEN
RAISE_APPLICATION_ERROR;
END IF;
END LOOP;
END;
/
I also suggest you get rid of the DELETE statement in the trigger as it will delete the contents of TEMP_BRANCH the first time the trigger fires.

How to use when condition with subquery like "NOT IN" with trigger?

this is my code and i don't know what wrong this:
CREATE TRIGGER trigger_before_insert_instruments
BEFORE INSERT ON Instruments
FOR EACH ROW
WHEN (NEW.type NOT IN (SELECT type FROM Types))
EXECUTE PROCEDURE
insert_new_type_or_famille();
But the console return me an error like "you can use subquery in the when condition...
Use if in the body of the trigger. That would look something like this:
CREATE TRIGGER trigger_before_insert_instruments
BEFORE INSERT ON Instruments
FOR EACH ROW
BEGIN
IF NOT EXISTS (SELECT 1 FROM Types WHERE :NEW.type = t.Types) THEN
EXECUTE PROCEDURE insert_new_type_or_famille();
END IF;
END;

Count(*) not working properly

I create the trigger A1 so that an article with a certain type, that is 'Bert' cannot be added more than once and it can have only 1 in the stock.
However, although i create the trigger, i can still add an article with the type 'Bert'. Somehow, the count returns '0' but when i run the same sql statement, it returns the correct number. It also starts counting properly if I drop the trigger and re-add it. Any ideas what might be going wrong?
TRIGGER A1 BEFORE INSERT ON mytable
FOR EACH ROW
DECLARE
l_count NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT COUNT(*) INTO l_count FROM mytable WHERE article = :new.article;
dbms_output.put_line('Count: ' || l_count);
IF l_count >0 THEN
IF(:new.TYPEB = 'Bert') THEN
dbms_output.put_line('article already exists!');
ROLLBACK;
END IF;
ELSIF (:new.TYPEB = 'Bert' AND :new.stock_count>1) THEN
dbms_output.put_line('stock cannot have more than 1 of this article with type Bert');
ROLLBACK;
END IF;
END;
This is the insert statement I use:
INSERT INTO mytable VALUES('Chip',1,9,1,'Bert');
A couple of points. First, you are misusing the autonomous transaction pragma. It is meant for separate transactions you need to commit or rollback independently of the main transaction. You are using it to rollback the main transaction -- and you never commit if there is no error.
And those "unforeseen consequences" someone mentioned? One of them is that your count always returns 0. So remove the pragma both because it is being misused and so the count will return a proper value.
Another thing is don't have commits or rollbacks within triggers. Raise an error and let the controlling code do what it needs to do. I know the rollbacks were because of the pragma. Just don't forget to remove them when you remove the pragma.
The following trigger works for me:
CREATE OR REPLACE TRIGGER trg_mytable_biu
BEFORE INSERT OR UPDATE ON mytable
FOR EACH ROW
WHEN (NEW.TYPEB = 'Bert') -- Don't even execute unless this is Bert
DECLARE
L_COUNT NUMBER;
BEGIN
SELECT COUNT(*) INTO L_COUNT
FROM MYTABLE
WHERE ARTICLE = :NEW.ARTICLE
AND TYPEB = :NEW.TYPEB;
IF L_COUNT > 0 THEN
RAISE_APPLICATION_ERROR( -20001, 'Bert already exists!' );
ELSIF :NEW.STOCK_COUNT > 1 THEN
RAISE_APPLICATION_ERROR( -20001, 'Can''t insert more than one Bert!' );
END IF;
END;
However, it's not a good idea for a trigger on a table to separately access that table. Usually the system won't even allow it -- this trigger won't execute at all if changed to "after". If it is allowed to execute, one can never be sure of the results obtained -- as you already found out. Actually, I'm a little surprised the trigger above works. I would feel uneasy using it in a real database.
The best option when a trigger must access the target table is to hide the table behind a view and write an "instead of" trigger on the view. That trigger can access the table all it wants.
You need to do an AFTER trigger, not a BEFORE trigger. Doing a count(*) "BEFORE" the insert occurs results in zero rows because the data hasn't been inserted yet.

Oracle Trigger on Insert or Delete or Update

Trying to create an Oracle trigger that runs after a table is updated in any way. I've been googling this all morning and came up with this:
CREATE OR REPLACE TRIGGER gb_qty_change
AFTER UPDATE OR INSERT OR DELETE ON F_ITEM_STORE
FOR EACH ROW
DECLARE
v_qty V_AD_ON_HAND%rowtype;
v_isbn TD_ITEM_DESCRIPTION.TD_IDENTIFIER%type;
BEGIN
delete from gb_transaction where gb_tide = :new.ITST_ITEM_TIDE_CODE;
select TD_IDENTIFIER INTO v_isbn from TD_ITEM_DESCRIPTION where TD_TIDE = :new.ITST_ITEM_TIDE_CODE;
select * INTO v_qty from V_AD_ON_HAND where ITST_ITEM_TIDE_CODE = :new.ITST_ITEM_TIDE_CODE;
insert into gb_transaction(gb_tide, gb_isbn, gb_used_on_hand, gb_new_on_hand)
values(:new.ITST_ITEM_TIDE_CODE, v_isbn, v_qty.USED_ON_HAND, v_qty.NEW_ON_HAND);
END;
/
I'm trying to keep it to a single record per TIDE_CODE in the new table.
V_AD_ON_HAND is a view that pulls an inventory count.
gb_transaction is my new table where I'm logging these events
Comparing it to other peoples code it looks like it should run but I'm getting "Warning: Trigger created with compilation errors."
The problem, I believe is with the :new designations for a delete trigger. There is, after all, no NEW value as the record is expunged. You can only access the :OLD values on delete.
if you section the trigger by operation, you can do this.
CREATE OR REPLACE TRIGGER gb_qty_change
AFTER UPDATE OR INSERT OR DELETE ON F_ITEM_STORE
FOR EACH ROW
DECLARE
v_qty V_AD_ON_HAND%rowtype;
v_isbn TD_ITEM_DESCRIPTION.TD_IDENTIFIER%type;
BEGIN
IF INSERTING or UPDATING then
... insert your existing code
ELSE
... do something similar with the :old values for the deleting case
end if;
END;
/
Incidentally, it is generally helpfull if you tell us WHAT the error is, not just that you had one. If compiling via SQL*Plus script, after the forward slash call to compile the trigger after the "end;" statement, add a line that says:
SHOW ERRORS TRIGGER YOUR_TRIGGER_NAME;