This is my code:
create trigger TriggerAdresse1
before INSERT
on Adresse
for each row
declare
enthaelt boolean;
begin
if ((Provinz in (select Name from Provinz2)) and (Laendercode in (select Laendercode from Provinz2))) then
enthaelt := true;
end if;
if(enthaelt:=false) then
rollback;
end if;
end;
I am trying to cancel the insert if the attribute Provinz or Laendercode isn´t in the table Provinz2.
Datagrip says it´s not valid...
Thanks for your help!
Best regards
A BOOLEAN value in Oracle can have three values: TRUE, FALSE and NULL, you did not initialize the variable.
You have to refer to new values (by default with :new)
By default a trigger cannot contain COMMIT or ROLLBACK
Your code must be this:
create trigger TriggerAdresse1
before INSERT
on Adresse
for each row
declare
enthaelt INTEGER;
begin
SELECT COUNT(*)
INTO enthaelt
FROM Provinz2
WHERE Name = :new.Provinz
AND Laendercode = :new.Laendercode;
if enthaelt = 0 then
RAISE_APPLICATION_ERROR(-20001, 'Provinz oder Ländercode ungültig');
end if;
end;
However, your requirement should be better implemented with FOREIGN KEY Constraint
alter table Adresse add constraint Provinz_FK FOREIGN KEY (Provinz)
references Provinz2 (Name);
alter table Adresse add constraint Laendercode_FK FOREIGN KEY (Laendercode)
references Provinz2(Laendercode);
Most likely Laendercode is not a UNIQUE key, but Name+Laendercode is. Then it would be this:
alter table Adresse add constraint Provinz_FK FOREIGN KEY (Provinz, Laendercode)
references Provinz2 (Name, Laendercode);
if(enthaelt:=false) then
Through this statement your intention is to perform a comparison however := does the assignment. So whenever you are comparing, you should use as below:
if(enthaelt=false) then
I think you intend to check the values being inserted so you really intend:
begin
enthaelt := false;
if ((:new.Provinz in (select Name from Provinz2)) and (:new.Laendercode in (select Laendercode from Provinz2))) then
enthaelt := true;
end if;
if(enthaelt:=false) then
rollback;
end if;
end;
You also need to initialize the variable.
That said, the correct way to implement this logic is with a foreign key constraint. It is sad that you are being taught to do this check with a trigger.
Related
As discussed here, I'm unable to use :OLD and :NEW on columns with collation other than USING_NLS_COMP. I'm trying to find a way around this but haven't been successful so far.
This is the original trigger:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
AFTER UPDATE ON PERSONS
FOR EACH ROW
begin
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := :old.SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := :new.SalutationTitle;
end;
This is what I've tried:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
FOR UPDATE ON Persons
COMPOUND TRIGGER
TYPE Persons_Record IS RECORD (
SalutationTitle NVARCHAR2(30)
);
TYPE Persons_Table IS TABLE OF Persons_Record INDEX BY PLS_INTEGER;
gOLD Persons_Table;
gNEW Persons_Table;
BEFORE EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gOLD
FROM Persons
WHERE ID = :OLD.ID;
END BEFORE EACH ROW;
AFTER EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gNEW
FROM Persons
WHERE ID = :NEW.ID;
END AFTER EACH ROW;
AFTER STATEMENT IS BEGIN
FOR i IN 1 .. gNEW.COUNT LOOP
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := gOLD(i).SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := gNEW(i).SalutationTitle;
END LOOP;
END AFTER STATEMENT;
END;
This results in error ORA-04091. I've also tried moving the select into the AFTER STATEMENT section which works, but there is no way to access the old values. If somebody has a solution for this it would be most appreciated.
EDIT:
I created a minimal reproducible example:
CREATE TABLE example_table (
id VARCHAR2(10),
name NVARCHAR2(100)
);
CREATE TABLE log_table (
id VARCHAR2(10),
new_name NVARCHAR2(100),
old_name NVARCHAR2(100)
);
CREATE OR REPLACE TRIGGER example_trigger
AFTER UPDATE ON example_table
FOR EACH ROW BEGIN
INSERT INTO log_table VALUES(:old.id, :new.name, :old.name);
END;
INSERT INTO example_table VALUES('01', 'Daniel');
-- this works as expected
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
CREATE TABLE example_table (
id VARCHAR2(10),
-- this is the problematic part
name NVARCHAR2(100) COLLATE XCZECH_PUNCTUATION_CI
);
INSERT INTO example_table VALUES('01', 'Daniel');
-- here nothing is inserted into log_example, if you try to
-- recompile the trigger you'll get error PLS-00049
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
DROP TABLE log_table;
DROP TRIGGER example_trigger;
In the discussion you reference a document concerning USING_NLS_COMP. That has nothing to do with the error you are getting. The error ORA-04091 is a reference to the table that fired the trigger (mutating). More to come on this. I am not saying you do not have USING_NLS_COMP issues, just that they are NOT causing the current error.
There are misconceptions shown in your trigger. Beginning with the name itself; you should avoid the prefix SYS. This prefix is used by Oracle for internal objects. While SYS prefix is not specifically prohibited at best it causes confusion. If this is actually created in the SYS schema then that in itself is a problem. Never use SYS schema for anything.
There is no reason to create a record type containing a single variable, then create a collection of that type, and finally define variables of the collection. Just create a collection to the variable directly, and define variables of the collection.
The bulk collect in the select statements is apparently misunderstood as used. I assume you want to collect all the new and old values in the collections. Bulk collect however will not do this. Each time bulk collect runs the collection used is cleared and repopulated. Result being the collection contains only the only the LAST population. Assuming id is unique the each collection would contain only 1 record. And now that brings us to the heart of the problem.
The error ORA-04091: <table name> is mutating, trigger/function may not see it results from attempting to SELECT from the table that fired the trigger; this is invalid. In this case the trigger fired due to a DML action on the persons table as a result you cannot select from persons in a row level trigger (stand alone or row level part of a compound trigger. But it is not needed. The pseudo rows :old and :new contain the complete image of the row. To get a value just reference the appropriate row and column name. Assign that to your collection.
Taking all into account we arrive at:
create or replace trigger personssalutation
for update
on persons
compound trigger
type persons_table is table of
persons.salutationtitle%type;
gold persons_table := persons_table();
gnew persons_table := persons_table();
before each row is
begin
gold.extend;
gold(gold.count) := :old.salutationtitle;
end before each row;
after each row is
begin
gnew.extend;
gold(gold.count) := :new.salutationtitle;
end after each row;
after statement is
begin
for i in 1 .. gnew.count loop
state_00.salutations_todelete(state_00.salutations_todelete.count + 1) := gold(i);
state_00.salutations_toinsert(state_00.salutations_toinsert.count + 1) := gnew(i);
end loop;
end after statement;
end personssalutation;
NOTE: Unfortunately you did not provide sample data, nor description of the functions in the AFTER STATEMENT section. Therefore the above is not tested.
When I create the below tables and trigger, I'm getting a compiling bad bind error.
CREATE TABLE cteam_ExpenseItem (
ExpenseNo NUMBER
);
CREATE TABLE cteam_ExpenseReport (
ERSubmitNo NUMBER
);
CREATE OR REPLACE TRIGGER cteam_Trigger3
BEFORE INSERT OR UPDATE OF ExpenseNo ON cteam_ExpenseItem
FOR EACH ROW
DECLARE
vA cteam_ExpenseItem.ExpenseNo%TYPE;
BEGIN
SELECT ExpenseNo
INTO vA
FROM cteam_ExpenseItem
WHERE ExpenseNo = :NEW.ERSubmitNo;
IF vA <= ERSubmitNo THEN
RAISE_APPLICATION_ERROR(-20000, 'Error');
END IF;
END;
I'm getting a bad bind error for 'NEW.ERSUBMITNO'.
How do I go about solving this?
As #stickybit pointed out in a comment, there is no ERSubmitNo column on cteam_ExpenseItem. But even if there was, you don't want to try reading from the table your trigger is defined on - you're likely to get a TABLE IS MUTATING, TRIGGER CANNOT SEE IT error. Instead, use the value from the :OLD pseudo-row:
CREATE OR REPLACE TRIGGER cteam_Trigger3
BEFORE INSERT OR UPDATE OF ExpenseNo ON cteam_ExpenseItem
FOR EACH ROW
DECLARE
vA cteam_ExpenseItem.ExpenseNo%TYPE;
BEGIN
IF :OLD.ExpenseNo <= :NEW.ExpenseNo THEN
RAISE_APPLICATION_ERROR(-20000, 'Error');
END IF;
END;
I'm guessing that's what you're trying to do - if not you can fold, spindle, or mutilate as necessary. :-)
declare
begin
for i in (select aid ,address from address)
loop
for j in (select aid ,address from address )
loop
if i.address=j.address then
if i.aid!=j.aid then
update employee_add
set aid=i.aid
where aid=j.aid;
delete from address
where aid=i.aid;
end if;
end if;
end loop;
end loop;
end;
/
This code works fine as for loop. After that it shows error :------
*Cause: A foreign key value has no matching primary key value.
*Action: Delete the foreign key or add a matching primary key.
I have tables employee[eid (primary key) ,ename] ,address[aid (primary
key),address],and many to many relation tableemployee_add[eid,aid].
Please help! Thank in Advance :)
You can use only one loop statement and variables( v_address and v_aid ) to make a comparison between the rows as in the block below :
declare
v_address address.address%type;
v_aid address.aid%type;
begin
for i in (select aid ,address from address order by aid)
loop
if nvl(v_address,'')=i.address then
update employee_add set aid=v_aid where aid=i.aid;
delete address where aid=i.aid;
else
v_address := i.address;
v_aid := i.aid;
end if;
end loop;
end;
Imagine this two tables
CREATE TABLE A
(
idA smallint primary key,
idP smallint ,
...
);
CREATE TABLE P
(
idP smallint primary key,
Type char , (ex:A, B, C)
...
);
I would like to behaviour like if in table A doesnt have the same type as in table P, is not for example a worker
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
BEGIN
IF((SELECT tp.Type
FROM P tp
WHERE tp.idP=:new.idP)!='W')
THEN RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;
I believe somethin is wrong
IN PL/SQL you need to select into a variable. But gmiley is correct, a foreign key or virtual column with an index is a better solution.
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
v_type CHAR(3);
BEGIN
SELECT tp.Type
INTO v_type
FROM P tp
WHERE tp.idP=:new.idP;
IF v_type != 'W'
THEN
RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;
You have to:
rename the column 'type' as this is a keyword and you'll get a lot of troubles when using it
add variable x to the declare section
use SELECT col INTO x construct
use x in the IF construct
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.