pl/sql trigger integrity constraint issue - oracle

I was tasked to create triggers to insert into a log table when an order table was
Inserted into
updated
also insert/delete/update was to be disabled between 5pm friday and 9am monday. The solution that follows covers all this, however because it is a before trigger I have had to turn off integrity constraints (does this matter for a log table?)
Does anyone have any suggestions how I might be able to do this AND keep the integrity constraints (on the logono column)?
I was thinking about 11G compunt triggers but it was sugegsted in a previous answer that this would not be an appropriate use, plus the issue with backwards compatibility.
CREATE OR REPLACE TRIGGER log_order
BEFORE INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
DECLARE out_of_hours EXCEPTION;
BEGIN
IF INSERTING THEN
IF
TO_NUMBER( TO_CHAR( SYSDATE, 'DHH24' ) ) BETWEEN 109 AND 517 THEN
insert into order_log values
(order_log_PK.nextval,
(select user from dual),
:NEW.ono,
(select SYSDATE from dual),
'Order Inserted' ) ;
ELSE
RAISE out_of_hours;
END IF;
END IF;
IF UPDATING THEN
IF
TO_NUMBER( TO_CHAR( SYSDATE, 'DHH24' ) ) BETWEEN 109 AND 517 THEN
insert into order_log values
(order_log_PK.nextval,
(select user from dual),
:NEW.ono,
(select SYSDATE from dual),
'order updated' ) ;
ELSE
RAISE out_of_hours;
END IF;
END IF;
IF DELETING THEN
IF
TO_NUMBER( TO_CHAR( SYSDATE, 'DHH24' ) ) BETWEEN 109 AND 517
THEN
RAISE out_of_hourS;
END IF;
END IF;
EXCEPTION
WHEN out_of_hours THEN
dbms_output.put_line('there is not privelages at this time');
RAISE_APPLICATION_ERROR(-20001, 'CANNOT UPDATE OUT OF HOURS');
END;
thanks
EDIT
The integrity issue came about becaue the :NEW.ono value going into the Log Table is not yet inserted into the order table due to this being a before trigger, therefore violating the logono Foreign Key.

You can simplify your trigger so that you only check the Monday 9am - Friday 5pm condition once, so that you only have one place where you insert into the log table, and to eliminate the need for an extra exception and an exception handler.
You can make the trigger an AFTER INSERT trigger so that the trigger executes after the data already exists in ORDERS so that you could create a foreign key constraint in ORDER_LOG that references the ONO key in ORDERS. However, it would seem very odd to want to create that sort of constraint. If you create your log table as a child of the base table, that would mean that you either would never be able to delete a row from ORDERS, since there would always be a child row, or that you would need to delete all the rows from ORDER_LOG for that ONO before doing the delete. Neither of those is generally particularly desirable-- in general, the point of having a log table is that it will log all the operations, not just the operations on rows in the base table that are still present.
CREATE OR REPLACE TRIGGER log_order
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
DECLARE
l_operation varchar2(30);
BEGIN
if to_number( to_char( sysdate, 'ddhh24' ) ) between 109 and 517
then
if inserting
then
l_operation := 'Order inserted';
elsif updating
then
l_operation := 'Order updated';
end if;
-- Note that I'm guessing at the names of the columns in order_log
insert into order_log( order_log_pk,
modify_user,
ono,
modify_date,
description )
values( order_log_PK.nextval,
user,
:new.ono,
sysdate,
l_operation );
else
raise_application_error( -20001, 'Cannot update out of hours' );
end if;
END;
While it may be syntactically valid to do an INSERT into a table without listing the columns you are inserting into, doing so is dangerous. If you add another column in the future, even if it is not required, your INSERT will start failing. If you list the columns explicitly, the trigger will continue to work. It is also relatively hard to spot bugs where data is being inserted into the wrong column if you don't list the columns explicitly. I guessed at what the columns were-- you'll have to substitute the proper column names.

Related

Making a trigger with RAISERROR [duplicate]

Hello fellow programmers and happy new year to you all!
I have few university tasks for winter break and one of them is to create trigger on table:
PERSON(ID, Name, Surname, Age);
Trigger is supposed to inform user when they have inserted row with invalid ID. Vadility criteria is that ID is 11 digits long.
I tried to write solution like this:
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
DECLARE
idNew VARCHAR(50);
lengthException EXCEPTION;
BEGIN
SELECT id INTO idNew FROM INSERTED;
IF LENGTH(idNew) <> 11 THEN
RAISE lengthException;
END IF;
EXCEPTION
WHEN lengthException THEN
dbms_output.put_line('ID for new person is INVALID. It must be 11 digits long!');
END;
Then I realized that INSERTED exists only in sqlserver and not in oracle.
What would you suggest I could do to fix that?
Thanks in advance!
Do you want to raise an exception (which would prevent the insert from succeeding)? Or do you want to allow the insert to succeed and write a string to the dbms_output buffer that may or may not exist and may or may not be shown to a human running the insert?
In either case, you'll want this to be a row-level trigger, not a statement-level trigger, so you'll need to add the for each row clause.
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
FOR EACH ROW
If you want to raise an exception
BEGIN
IF( length( :new.id ) <> 11 )
THEN
RAISE_APPLICATION_ERROR( -20001,
'The new ID value must have a length of 11' );
END IF;
END;
If you want to potentially print output but allow the insert to succeed
BEGIN
IF( length( :new.id ) <> 11 )
THEN
dbms_output.put_line( 'The new ID value must have a length of 11' );
END IF;
END;
Of course, in reality, you would never use a trigger for this sort of thing. In the real world, you would use a constraint.

How can we Create a Trigger(or any Object) to insert rows into Attendance table when a certain time of day passes?

I am new to PL/SQL and Oracle APEX. In oracle APEX, I'm trying to create a trigger that will fire when employees are not present after a certain time, say for example 12:00 PM and it's not friday.
I have these columns in the Attendances Table: emp_id, work_date, attend_stat.
The emp_id is a foreign key referenced from Employees Table
When it's past 12:00 PM, and there was no attendance entry for a particular employee, I want a row inserted for that employee like this:
insert into attendances(emp_id, work_date, attend_Stat)
values("ID OF NOT PRESENT EMPLOYEE", sysdate, 'A');
I have written this pl/sql statement but I cannot find a way to implement it or figure out will it even work.
declare
cursor c_emp_id is
select emp_id from employees;
cursor c_emp_at is
select emp_id from attendances where work_date = sysdate and to_char(sysdate,'DAY') <> 'FRIDAY';
begin
for i in c_emp_id loop
for a in c_emp_at loop
if i.emp_id <> a.emp_id then
insert into attendances(emp_id, work_date, attend_stat)
values(i.emp_id, systimestamp, 'A');
end if;
end loop;
end loop;
exception
when no_data_found then
for i in c_emp_id loop
insert into attendances(emp_id, work_date, attend_stat)
values(i.emp_id, systimestamp, 'A');
end loop;
end;
How can I achieve this?
You need to save the code in a package or in a stored procedure and set it to run as a database job. See the documentation for details.
Please note the doc is for version 11 of the database, check the version you are on
select version from v$instance
and google for actual docs

Statement level trigger to enforce a constraint

I am trying to implement a statement level trigger to enforce the following "An applicant cannot apply for more than two positions in one day".
I am able to enforce it using a row level trigger (as shown below) but I have no clue how to do so using a statement level trigger when I can't use :NEW or :OLD.
I know there are alternatives to using a trigger but I am revising for my exam that would have a similar question so I would appreciate any help.
CREATE TABLE APPLIES(
anumber NUMBER(6) NOT NULL, /* applicant number */
pnumber NUMBER(8) NOT NULL, /* position number */
appDate DATE NOT NULL, /* application date*/
CONSTRAINT APPLIES_pkey PRIMARY KEY(anumber, pnumber)
);
CREATE OR REPLACE TRIGGER app_trigger
BEFORE INSERT ON APPLIES
FOR EACH ROW
DECLARE
counter NUMBER;
BEGIN
SELECT COUNT(*) INTO counter
FROM APPLIES
WHERE anumber = :NEW.anumber
AND to_char(appDate, 'DD-MON-YYYY') = to_char(:NEW.appDate, 'DD-MON-YYYY');
IF counter = 2 THEN
RAISE_APPLICATION_ERROR(-20001, 'error msg');
END IF;
END;
You're correct that you don't have :OLD and :NEW values - so you need to check the entire table to see if the condition (let's not call it a "constraint", as that term has specific meaning in the sense of a relational database) has been violated:
CREATE OR REPLACE TRIGGER APPLIES_AIU
AFTER INSERT OR UPDATE ON APPLIES
BEGIN
FOR aRow IN (SELECT ANUMBER,
TRUNC(APPDATE) AS APPDATE,
COUNT(*) AS APPLICATION_COUNT
FROM APPLIES
GROUP BY ANUMBER, TRUNC(APPDATE)
HAVING COUNT(*) > 2)
LOOP
-- If we get to here it means we have at least one user who has applied
-- for more than two jobs in a single day.
RAISE_APPLICATION_ERROR(-20002, 'Applicant ' || aRow.ANUMBER ||
' applied for ' || aRow.APPLICATION_COUNT ||
' jobs on ' ||
TO_CHAR(aRow.APPDATE, 'DD-MON-YYYY'));
END LOOP;
END APPLIES_AIU;
It's a good idea to add an index to support this query so it will run efficiently:
CREATE INDEX APPLIES_BIU_INDEX
ON APPLIES(ANUMBER, TRUNC(APPDATE));
dbfiddle here
Best of luck.
Your rule involves more than one row at the same time. So you cannot use a FOR ROW LEVEL trigger: querying on APPLIES as you propose would hurl ORA-04091: table is mutating exception.
So, AFTER statement it is.
CREATE OR REPLACE TRIGGER app_trigger
AFTER INSERT OR UPDATE ON APPLIES
DECLARE
cursor c_cnt is
SELECT 1 INTO counter
FROM APPLIES
group by anumber, trunc(appDate) having count(*) > 2;
dummy number;
BEGIN
open c_cnt;
fetch c_cnt in dummy;
if c_cnt%found then
close c_cnt;
RAISE_APPLICATION_ERROR(-20001, 'error msg');
end if;
close c_cnt;
END;
Obviously, querying the whole table will be inefficient at scale. (One of the reasons why triggers are not recommended for this sort of thing). So this is a situation in which we might want to use a compound trigger (assuming we're on 11g or later).

SQL trigger - nesting INSERT INTO in IF condition

I want to create trigger registring in new table employees whom salary is being raised above 5000 and didn't get salary higher than 5000 so far.
Trigger I wrote is returning error Error(2,41): PL/SQL: ORA-00984: column not allowed here.
Here's my trigger:
CREATE OR REPLACE TRIGGER emp_gotrich_trig BEFORE UPDATE OF salary ON employees
FOR EACH ROW BEGIN
IF :NEW.salary>5000 AND :OLD.salary<=5000 THEN
INSERT INTO emp_gotrich VALUES (employee_id, SYSDATE, :OLD.salary, :NEW.salary);
END IF;
END;
And here's emp_gotrich table:
CREATE TABLE emp_gotrich ( emp_id NUMBER(6), raise_date DATE, old_sal NUMBER(8,2), new_sal NUMBER(8,2) );
I guess that INSERT statemet isn't nested properly but i don't know what should i change.
I also tried to use 'WHEN' but i dont know where should i omitt colons, so it doeasn't work too.
CREATE OR REPLACE TRIGGER emp_getrich_log BEFORE UPDATE OF salary ON employees FOR EACH ROW
WHEN
NEW.salary>5000 AND OLD.salary<=5000;
BEGIN
INSERT INTO emp_gotrich VALUES(employee_id, SYSDATE, :OLD.salary, :NEW.salary);
END;
Please, help me find a way to run it.
You forgot to specify :OLD or :NEW on the employee_id value in your INSERT statement. I believe it should be:
INSERT INTO emp_gotrich
(EMP_ID, RAISE_DATE, OLD_SAL, NEW_SAL)
VALUES
(:OLD.employee_id, SYSDATE, :OLD.salary, :NEW.salary);
I suggest that a field list, such as the one I added, should always be included in an INSERT statement.

fire trigger after insert in oracle

I'm very new for trigger, now this what i was trying. I've two tables INSERTED and ORDER_INFO, both have the same column name ORDER_ID, ORDER_DATE. I've scenario, where client will be placing his/her order then, order information will be stored into INSERTED table, then by using this trigger, it'll insert into another table ORDER_INFO after satisfying the condition, which has been written.
create trigger tri_check
AFTER INSERT ON inserted FOR EACH ROW
DECLARE
v_date DATE;
BEGIN
SELECT order_date INTO v_date FROM inserted;
if (v_date)< (sysdate + 2) then
raiserror('You cannot take an order to be delivered less than 2 days from now',16, 1);
else
INSERT INTO orders_info
( order_id,order_date)
VALUES
(:new.order_id,v_date);
end if;
end;
But, when i'm executing the above trigger, then i'm getting this error.
ERROR at line 8: PL/SQL: SQL Statement ignored
6. SELECT order_date INTO v_date FROM inserted;
7. if (v_date)< (sysdate + 2) then
8. raiserror('You cannot take an order to be delivered less than 2 days from now',16, 1);
9. else
10. INSERT INTO orders_info
EDIT
Now, i made the same structure table into SYSTEM user, and got the same error. Table or View does not exist
Need help !! Thanks in advance !!
The message seems to indicate a problem with the 'raiserror' procedure. I'm not familiar with such a procedure in standard PL/SQL - did you mean RAISE_APPLICATION_ERROR? However, and perhaps more to the point, when using a trigger there's no need to do a SELECT from the table. All the data being inserted is available to the trigger. I suggest changing your trigger to be something like the following:
create trigger tri_check
AFTER INSERT ON inserted
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
if :new.ORDER_DATE < sysdate + INTERVAL '2' DAY then
RAISE_APPLICATION_ERROR(-20000, 'You cannot take an order to be delivered less than 2 days from now');
else
INSERT INTO orders_info
(order_id, order_date)
VALUES
(:new.order_id, :new.ORDER_DATE);
end if;
end TRI_CHECK;
Share and enjoy.
You can just use the :NEW and :OLD values instead of your select:
CREATE TRIGGER tri_check
AFTER INSERT
ON inserted
FOR EACH ROW
DECLARE
BEGIN
IF :new.order_date < (SYSDATE + 2)
THEN
raiserror (
'You cannot take an order to be delivered less than 2 days from now',
16,
1);
ELSE
INSERT INTO orders_info (order_id, order_date)
VALUES (:new.order_id, :new.order_date);
END IF;
END;
What is your raiserror procedure? Do you have access permissions granted on it?
Hope it helps...
EDIT:
OK, from your error, and the error you posted on #Bob Jarvis' answer, you might not have INSERT privilege on the ORDERS_INFO table. You also should check your permissions on the INSERTED table too.
Check your permissions with your DBA.
If raiserror is not a defined procedure or you don't have access to it then use the RAISE_APPLICATION_ERROR method for raising an error as Bob suggests.

Resources