Oracle SQL trigger - DBMS_OUTPUT.PUT_LINE - sql

I'm creating a trigger within my database, I came across two error that I am not able to fix, I'm pretty sure that those two are relating to my use of DBMS_OUTPUT.PUT_LINE, the rest of the statement does not cause any errors, although it had before.
Errors:
Error(5,3): PL/SQL: SQL Statement ignored
Error(5,15): PL/SQL: ORA-00903: invalid table name
Code:
CREATE TRIGGER INVOICES
BEFORE INSERT OR UPDATE ON BRUINVOICE
FOR EACH ROW
BEGIN
IF :new.BRU_DATE < :new.BRU_PAID_DATE THEN
DBMS_OUTPUT.PUT_LINE('You cannot do that');
ELSE
INSERT INTO table BRUINVOICE
values
from inserted;
END IF;
END;

Check constraints are a better choice (performance-wise) than triggers when it comes to record level validation:
ALTER TABLE bruinvoice
ADD CONSTRAINT validate_bru_date CHECK (BRU_DATE < BRU_PAID_DATE);
Inserting invalid data will raise an error message like the following:
scott#ORCL> insert into bruinvoice values ('21-DEC-14','20-DEC-14');
insert into bruinvoice values ('21-DEC-14','20-DEC-14')
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.VALIDATE_BRU_DATE) violated

I fully agree with cstotzer, a check constraint is much better in your situation at should be the preferred way of doing it. However, just for information this would be the trigger syntax:
CREATE TRIGGER INVOICES
BEFORE INSERT OR UPDATE ON BRUINVOICE
FOR EACH ROW
BEGIN
IF :new.BRU_DATE < :new.BRU_PAID_DATE THEN
RAISE_APPLICATION_ERROR(-20001, 'You cannot do that');
END IF;
END;
You don't need any ELSE, your INSERT or UPDATE will be simply executed in this case.

Related

Trigger created with compiling errors on unique value

I'm trying to create a trigger that checks the value of a column whenever I have an insert or an update on the table, the value of columnX must be unique:
tableX(ID, ..., columnX)
CREATE or replace TRIGGER tableX_uk
BEFORE INSERT OR UPDATE ON tableX
FOR EACH ROW
BEGIN
if(:new.columnX in (select T.columnX from tableX T)) then
Raise_Application_Error(-20001, 'Already existing');
end if;
End;
It shows that the trigger is created with compiling errors.
I couldn't find any error here, can someone help me please ? Thank you !
It shows that the trigger is created with compiling errors. I couldn't find any error here
Errors for stored PL/SQL can be found in user/all/dba_errors. Desktop tools such as SQL Developer, Toad, PL/SQL Developer etc will display them automatically but that's where they get the details from.
DBFiddle
In this case the first error is from
if(:new.columnX in (select t.columnX from tableX t))
which gives
PLS-00405: subquery not allowed in this context
because if x in (select...) isn't valid PL/SQL syntax. You have to do the select as a separate step. Fixing that will give you code that at least compiles, but still isn't ideal:
create or replace trigger tablex_uk_trg
before insert or update on tablex
for each row
declare
l_columnx_check number := 0;
begin
select count(*) into l_columnx_check
from tablex t
where t.columnx = :new.columnx
and rownum = 1;
if l_columnx_check > 0 then
raise_application_error(-20001, 'Value '||:new.columnx||' already exists');
end if;
end;
It's not ideal because firstly, a unique constraint is a far more efficient and self-documenting way to enforce uniqueness.
create table tablex (columnx number unique);
or better still
create table tablex (columnx number constraint tablex_uk unique);
or if it's the primary key
create table tablex (columnx number constraint tablex_pk primary key);
Now, anyone checking the table definition will see the unique constraint, the optimiser will use it in queries, it has a standard error code ORA-00001: unique constraint (WILLIAM.TABLEX_UK) violated and so on.
Secondly, the update part of the trigger won't work anyway. Oracle won't let a row-level update trigger query its own table:
insert into tablex (columnx) values (1);
update tablex set columnx = 1;
ORA-04091: table WILLIAM.TABLEX is mutating, trigger/function may not see it
ORA-06512: at "WILLIAM.TABLEX_UK_TRG", line 4
ORA-04088: error during execution of trigger 'WILLIAM.TABLEX_UK'
DBFiddle
And in any case the logic is missing some checks, because the update in my example should be valid. But I won't go into how you might fix that because the unique constraint is all you need.

When is FOR EACH ROW not needed in a BEFORE INSERT TRIGGER in Oracle?

I am new to PLSQL in Oracle. When I am learning about triggers, I have read from this source https://www.techonthenet.com/oracle/triggers/before_insert.php which says that when I create a BEFORE INSERT Trigger in Oracle, the FOR EACH ROW is NOT always needed, hence the syntax is enclosed by square brackets [ ]. I have written this simple trigger:
CREATE OR REPLACE TRIGGER enroll_time
BEFORE INSERT
ON ENROLL
FOR EACH ROW
BEGIN
:new.addtime := sysdate;
END;
/
If I remove the FOR EACH ROW in the above, I actually get an error:
Error report -
ORA-04082: NEW or OLD references not allowed in table level triggers
04082. 00000 - "NEW or OLD references not allowed in table level triggers"
*Cause: The trigger is accessing "new" or "old" values in a table trigger.
*Action: Remove any new or old references.
From the error message, it seems like if I use :new.[column_name], then FOR EACH ROW must have to exist. Why is this? Is there any example that FOR EACH ROW is NOT needed in a BEFORE INSERT TRIGGER in Oracle?
Is there any example that FOR EACH ROW is NOT needed in a BEFORE INSERT TRIGGER in Oracle?
Simple example of statement level trigger:
CREATE TABLE test_table(col VARCHAR2(10));
CREATE OR REPLACE TRIGGER enroll_time
BEFORE INSERT
ON ENROLL
BEGIN
INSERT INTO test_table(col)
SELECT 1 FROM dual;
END;
/
I highly recommend to read about compound trigger to understand when each part is fired.
Basically, if you need to use :OLD or :NEW pseudotables, you need a row level trigger. An example of a statement level trigger would be inserting a record into a table when another table is effected.

Trigger inputing information about action in table

I created table to store informations about time of any update, insert or delete data in one of tables.
CREATE TABLE dept_changes ( data DATE, action VARCHAR2(16) );
Now I want to create trigger inputing the data to table:
CREATE OR REPLACE TRIGGER dept_changes_trig AFTER UPDATE OR INSERT OR DELETE ON departments
DECLARE
action VARCHAR2(16);
BEGIN
IF UPDATING THEN
action:='upd';
END IF;
IF INSERTING THEN
action:='ins';
END IF;
IF DELETING THEN
action:='del';
END IF;
INSERT INTO DEPT_CHANGES (SYSDATE, action);
END;
I got 2 errors in line 12 (END IF of DELETING condition statement).
Error(12,5): PL/SQL: SQL Statement ignored
Error(12,46): PL/SQL: ORA-00926: missing VALUES keyword
I don't understand, what VALUES am I missing? What the trigger needs to work properly?
Use the values keyword
INSERT INTO DEPT_CHANGES VALUES (SYSDATE, action);
Insert Examples

PL/SQL Conditional statement using two tables within trigger

I'm working on a simple trigger that updates the Quantity on Hand of a product when adding a new invoice line.
I got that working fine; but I starting thinking that in a practical application it would be prudent to verify that the QOH is sufficient before allowing the update. I'm sure I could write a separate trigger for that, but I'd like to see if it's possible to join two tables for a conditional statement within a trigger.
This is as far as I've gotten; adding a SELECT statement anywhere in there causes all hell to break loose, so I'm a little stumped how I can declare the PRODUCT.P_QOH before calling the conditional.
CREATE OR REPLACE TRIGGER trg_prod_QOH_on_line_add
BEFORE INSERT ON LINE
FOR EACH ROW
BEGIN
IF :NEW.LINE_UNITS > PRODUCT.P_QOH THEN
RAISE_APPLICATION_ERROR(-20202, 'Insufficient quantity on hand');
ELSE
UPDATE PRODUCT
SET P_QOH = P+QOH - :NEW.LINE_UNITS;
WHERE PRODUC.P_CODE = :NEW.P_CODE;
END IF;
END;
/
This isn't a major problem for me, as I said there is probably a different way; I'm just starting to learn this stuff and would like to see what's possible. Thanks for your help.
You get into dangerous territory trying to enforce rules like this via triggers. The solution you are asking for is:
create or replace trigger trg_prod_qoh_on_line_add
before insert on line
for each row
declare
v_qoh product.p_qoh%type;
begin
select p_qoh
into v_qoh
from product
where product.p_code = :new.p_code;
if :new.line_units > v_qoh then
raise_application_error(-20202, 'Insufficient quantity on hand');
else
update product
set p_qoh = p_qoh - :new.line_units
where product.p_code = :new.p_code;
end if;
end;
However this is not a safe solution in a system with more than one concurrent user. Suppose product 'X' has p_qoh=10 and then 2 users do this:
user1> insert into line (p_code, line_units) values ('X', 7);
user2> insert into line (p_code, line_units) values ('X', 8);
user1> commit;
user2> commit;
Both sessions will see that 'X' has p_qoh = 10 so both will succeed and product.p_qoh will end up as -5. All is corrupt!
The safe solution would be to create a check constraint on product:
alter table product add constraint prod_qoh_chk check (p_qoh >= 0);
Now your trigger can be simply:
create or replace trigger trg_prod_qoh_on_line_add
before insert on line
for each row
begin
update product
set p_qoh = p+qoh - :new.line_units;
where produc.p_code = :new.p_code;
end;
This would raise a less friendly error message like:
ORA-02290: check constraint (MYSCHEMA.PROD_QOH_CHECK) violated
You can trap this in your trigger and give the message you want:
create or replace trigger trg_prod_qoh_on_line_add
before insert on line
for each row
begin
update product
set p_qoh = p+qoh - :new.line_units;
where produc.p_code = :new.p_code;
exception
when others then
if sqlerrm like 'ORA-02291:%(MYSCHEMA.PROD_QOH_CHECK)%' then
raise_application_error(-20202,'Insufficient quantity on hand');
else
raise;
end if;
end;
Now if we re-run the 2 user scenario above:
user1> insert into line (p_code, line_units) values ('X', 7);
user2> insert into line (p_code, line_units) values ('X', 8);
user1> commit;
At this point user2's insert fails with the error message:
ORA-20202: Insufficient quantity on hand

Oracle SQL Trigger

I want to prevent the database from storing any values bigger than 20 into a table.
CREATE OR REPLACE TRIGGER Dont_Allow
AFTER INSERT ON Cities
FOR EACH ROW
WHEN (new.IDCity > 20)
BEGIN
dbms_output.put_line(' Failed to insert ' || :new.IDCity);
delete from orase where IDCity=:new.IDCity;
END;
While this does work in terms of not actually adding anything with an ID > 20, every time the trigger tries to do its magic, this shows up:
ORA-04091: table SYSTEM.ORASE is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM.DONT_ALLOW", line 6
ORA-04088: error during execution of trigger 'SYSTEM.DONT_ALLOW'
What's a proper way of doing what I want?
EDIT:
I've decided to use a trigger for this:
After a new row is inserted into Employees, a trigger checks the new guy's salary and if it's above 21 units / hour, it takes 5% off management's bonus. Lame, but hey - I'm using a trigger to solve a problem I don't have: the outcome won't be pretty.
CREATE OR REPLACE TRIGGER Bite_Bonus
AFTER INSERT ON Employees
FOR EACH ROW
WHEN (new.HourSalary > 20)
BEGIN
update Management set Bonus = Bonus - 5/100 * Bonus;
END;
You shouldn't be using a TRIGGER for that, you should be using a CHECK, like CONSTRAINT city_id_below_20 CHECK (IDCity < 20). You can use ALTER TABLE ADD CONSTRAINT to put it on an existing table.
As TC1 indicated, the proper way to enforce this sort of requirement is to use a constraint.
If you are forced to use the inferior approach because this is a school assignment, you most likely want to raise an exception in your trigger
CREATE OR REPLACE TRIGGER Dont_Allow
BEFORE INSERT OR UPDATE ON Cities
FOR EACH ROW
WHEN (new.IDCity > 20)
BEGIN
RAISE_APPLICATION_ERROR( -20001, 'IDCity cannot exceed 20 so rejecting invalid value: ' || :new.IDCity );
END;
If you need to use a trigger for this, make it a BEFORE INSERT trigger, not an AFTER INSERT - you don't want that insert to happen at all. Trying to "undo" it after the fact is not a good approach.
To abort the insert, all you need to do is raise an exception within that trigger. Probably the best thing for this is to raise an application error.