Trigger to update week of the year - oracle

I want to write a trigger so that when decom_date is inserted or updated the week of the year is updated to the corresponding value.
This is what I have so far, but after inserting a date the week is still null.
create or replace trigger test_trigger
before insert on check_decom
for each row
begin
if inserting then
update check_decom set decom_week= (select to_char(to_date(decom_date,'DD-
MON-YY'),'WW') as week from check_decom) ;
end if;
end;
/
SQL> select * from check_decom;
DECOM_DATE DECOM_WEEK
------------------------------ ----------
23-JUN-17
What am I doing wrong?
Example for Week of a year
SQL> select to_char(to_date(sysdate,'DD-MON-YY'),'WW') as week from dual;
WE
--
28

You're doing a couple of things wrong, starting with date handling. Your decom_date column should be defined as a DATE column - it looks like it might be a string in your sample output. But your handling with sysdate is also wrong, as you're implicitly converting to a string in order to convert it back to a date, which is both pointless and prone to error as this might happen in a session which has different NLS settings. If your column is actually a DATE then you should not be calling to_date() against that either; and if it is a string then that conversion is valid but it should be a DATE.
Then your trigger is querying and trying to update the table that the trigger is against. With no data that doesn't error but doesn't do anything as there is no existing row to update - the one you are inserting doesn't exist yet. If there was data you would get a mutating table error, if you didn't get a too-many-rows exception from the select part.
Row-level triggers can access NEW and OLD pseudorecords to see and manipulate the affected row; you don't need to (and generally can't) use DML queries to access the data in the row you're manipulating.
If your table was defined with a date column and a number column:
create table check_decom(decom_date date, decom_week number);
then your trigger might look something like:
create or replace trigger test_trigger
before insert on check_decom
for each row
begin
if inserting then
:new.decom_week := to_number(to_char(:new.decom_date, 'WW'));
end if;
end;
/
although the if inserting check is a bit pointless as the trigger will only fire on insert anyway. Which in itself might be an issue; you perhaps want it to be set on update as well, but the logic the same, so would be:
create or replace trigger test_trigger
before insert or update on check_decom
for each row
begin
:new.decom_week := to_number(to_char(:new.decom_date, 'WW'));
end;
/
which does what you want:
insert into check_decom (decom_date) values (date '2017-06-23');
1 row inserted.
select * from check_decom;
DECOM_DAT DECOM_WEEK
--------- ----------
23-JUN-17 25
But I wouldn't do this with a trigger at all. From Oracle 11g you can use a virtual column instead:
create table check_decom (
decom_date date,
decom_week generated always as (to_number(to_char(decom_date, 'WW')))
);
Table CHECK_DECOM created.
insert into check_decom (decom_date) values (date '2017-06-23');
1 row inserted.
select * from check_decom;
DECOM_DAT DECOM_WEEK
--------- ----------
23-JUN-17 25

Related

I cant find solution for the trigger mutating in PL/SQL

So i am a newbie in PL/SQL, And i want to create a trigger in which a specific record salary can not be updated or deleted while other records of the table can. Suppose the record i want not to be able to update or delete its salary is EMPNO = 7839, The trigger gets created but when i update any records in EMP table it gives me error that ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it, Can someone give me a solution for this?
This is the code:
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
DECLARE
ROW_NUM NUMBER;
BEGIN
SELECT COUNT(*) INTO ROW_NUM FROM EMP WHERE EMPNO = 7839;
IF UPDATING('ROW_NUM') THEN
RAISE_APPLICATION_ERROR('-20000','CANT UPDATE/DELETE SALARY OF EMPNO = 7839');
END IF;
END PRACTICE_TRIGGER;
/
You can convert your code into this one :
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
BEGIN
IF :OLD.EMPNO = 7839 AND :OLD.SAL != NVL(:NEW.SAL,0) THEN
RAISE_APPLICATION_ERROR('-20000','CAN''T UPDATE SALARY OR DELETE THE ROW FOR EMPNO = '||:OLD.EMPNO);
END IF;
END;
/
Where
No query is not needed. Just new and old versions of the concerned
SAL values should be equal for an employee in order to keep that value(7839) to
be kept within the table. For DELETING case, the :NEW values for the columns will be NULL.
Those conditions are valid for both DELETING and UPDATING, so no
need to repeat them within the code. But a column cannot be be deleted, deletion of the whole record will be the case
Repeating the trigger name at the end is optional, so might be
removed.
Demo
For starters, your query is selecting the number of records, not the record identifier - it will never return "7839", only "1" or "0" for the number of records found. Also, you can't reference the table to which the trigger belongs from within the trigger (that's your mutating table error). Lastly, 'ROW_NUM' is not a column in your table, it is a variable in your trigger, so "IF UPDATING('ROW_NUM') would always be false, assuming it compiles at all.
The most basic form of what you're looking for would be this:
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
BEGIN
-- check to see if record being updated is restricted, then raise error
IF :OLD.EMPNO = 7839 THEN
RAISE_APPLICATION_ERROR('-20000','CANT UPDATE/DELETE SALARY OF EMPNO = 7839');
END IF;
END PRACTICE_TRIGGER;
/
That said, one obvious flaw in this approach is that the trigger as written doesn't prevent someone from changing the employee id, so theoretically if someone changed that first then the restriction on salary change would not work. A more effective approach would be a boolean column (true/false) that would identify locked records and a check to see if that flag was set. i would also recommend using a table API package to perform the actual DML operations rather than direct SQL commands, and avoid the use of triggers altogether if possible.

After Insert Trigger ORA-01422: fetch returns more than requested number of rows

Can anyone help me with this code below. There are 3 tables : Customer_A1, Reservation_A1 and Invoice_A1. I am writing a trigger that will execute every time a new reservation is made.
The trigger will pre-loaded the invoice table the information of invoice_id (inv_id), reservation_id (res_id), customer first name (cust_fname), customer last name (cust_lname) and reservation_start_date.
My code is below. There is no compilation errors when trigger is created. However when i insert a new row to Reservation table which makes the trigger execute, it inform me of that my trigger has an error of
ORA-01422: fetch returns more than requested number of rows.
CREATE OR REPLACE TRIGGER invoice_after_reservation_made
AFTER INSERT
ON RESERVATION_A1
FOR EACH ROW
DECLARE
inv_id INVOICE_A1.INV_ID%type;
res_id INVOICE_A1.res_id%type;
room_id INVOICE_A1.room_id%type;
cust_fname INVOICE_A1.cust_fname%type;
cust_lname INVOICE_A1.cust_lname%type;
reservation_start_date INVOICE_A1.reservation_start_date%type;
cust_id RESERVATION_A1.cust_id%type;
BEGIN
--read reservation_id
res_id:= :new.res_id;
--read room_id
room_id:= :new.room_id;
--read reservation_start_date
reservation_start_date:= :new.reservation_start_date;
--read customer_id
cust_id:= :new.cust_id;
--create new invoice_id
SELECT MAX(INVOICE_A1.inv_id)+1 INTO inv_id FROM INVOICE_A1;
-- import value from CUSTOMER_A1 table to variable cust_fname, cust_lname
Select CUSTOMER_A1.cust_fname,CUSTOMER_A1.cust_lname INTO
cust_fname,cust_lname
FROM CUSTOMER_A1
WHERE CUSTOMER_A1.cust_id=cust_id;
-- Insert record into invoice table
INSERT INTO INVOICE_A1
VALUES (inv_id,res_id,room_id,cust_fname,cust_lname,null,TO_DATE(TO_CHAR(reservation_start_date),'DD/MM/YYYY'),null);
END;
Note: I have looked up for solution on internet however no cigar though. People said the problem mostly come from Select statements that return more than one row. However my Select query in the code above return only one row. I also check the table's data, No entity and referential integrity are violated in 3 tables Customer_A1, Reservation_A1 and Invoice_A1. I even copy a code to a separate test procedure to print out all variables after reading inputs. The test procedure work well. I surrender now. Please help me with this problem. I am new . Thanks
The problem is in the statement
Select CUSTOMER_A1.cust_fname,CUSTOMER_A1.cust_lname INTO
cust_fname,cust_lname
FROM CUSTOMER_A1
WHERE CUSTOMER_A1.cust_id=cust_id;
You probably meant this to mean "Find data from CUSTOMER_A1 where CUSTOMER_A1.cust_id = the value of the variable 'cust_id'". Unfortunately, that's not how it's interpreted. The database is reading this as "Find data from CUSTOMER_A1 where CUSTOMER_A1.cust_id = CUSTOMER_A1.cust_id" - in other words, it's comparing the CUST_ID field of each row to itself, finding that they're equal (except in the case of NULL values), and returns data from that row.
A good rule to remember when writing PL/SQL is "Never give a variable the same name as a column you'll be manipulating". With this in mind, you might consider rewriting your trigger as:
CREATE OR REPLACE TRIGGER invoice_after_reservation_made
AFTER INSERT
ON RESERVATION_A1
FOR EACH ROW
DECLARE
vInv_id INVOICE_A1.INV_ID%type;
vRes_id INVOICE_A1.res_id%type;
vRoom_id INVOICE_A1.room_id%type;
vCust_fname INVOICE_A1.cust_fname%type;
vCust_lname INVOICE_A1.cust_lname%type;
vReservation_start_date INVOICE_A1.reservation_start_date%type;
vCust_id RESERVATION_A1.cust_id%type;
BEGIN
--read reservation_id
vRes_id:= :new.res_id;
--read room_id
vRoom_id:= :new.room_id;
--read reservation_start_date
vReservation_start_date:= :new.reservation_start_date;
--read customer_id
vCust_id:= :new.cust_id;
--create new invoice_id
SELECT MAX(INVOICE_A1.inv_id)+1 INTO vInv_id FROM INVOICE_A1;
-- import value from CUSTOMER_A1 table to variable cust_fname, cust_lname
Select CUSTOMER_A1.cust_fname,CUSTOMER_A1.cust_lname
INTO vCust_fname, vCust_lname
FROM CUSTOMER_A1
WHERE CUSTOMER_A1.cust_id=cust_id;
-- Insert record into invoice table
INSERT INTO INVOICE_A1
VALUES (vInv_id, vRes_id, vRoom_id, vCust_fname, vCust_lname, null,
TO_DATE(TO_CHAR(reservation_start_date),'DD/MM/YYYY'), null);
END invoice_after_reservation_made;

Insert in Merge not working in Oracle

I am new to Oracle. I have a table in Oracle which has 4 columns Period, Open_Flag,Creation_Dt,Updated_By.
The Period column is the Primary key of the table. I have created a proc which will check the value of period from input parameter in the table, if its existing, the value of Open_flag has to be updated else a new record shall be inserted.
create or replace
PROCEDURE PROC_REF_SAP_PERIOD(
V_PERIOD IN NUMBER,V_OPEN_FLAG IN VARCHAR2,V_CREATION_DT IN DATE,V_UPDATED_BY IN VARCHAR2)
AS
BEGIN
MERGE INTO REF_SAP_PERIOD T
USING (SELECT * FROM REF_SAP_PERIOD WHERE PERIOD=V_PERIOD )S
ON (T.PERIOD=S.PERIOD )
WHEN MATCHED THEN UPDATE SET OPEN_FLAG = V_OPEN_FLAG --WHERE PERIOD=V_PERIOD AND CREATION_DT=V_CREATION_DT AND UPDATED_BY=V_UPDATED_BY
WHEN NOT MATCHED THEN INSERT (PERIOD,OPEN_FLAG,CREATION_DT,UPDATED_BY) VALUES (V_PERIOD,V_OPEN_FLAG,V_CREATION_DT,V_UPDATED_BY);
END;
The issue is that the Update is working well in this case, however, the insert is not working. Please help.
You are merging table with itself, filtered by period. Obviously, it will never see your non-existent values in itself.
Try this line instead of your USING line:
using (select V_PERIOD "period" from dual)S

Trigger in oracle. Update field when insert or update another field

I need To update the field DATAMARKER"of my table LOG_ALARMA when I have one INSTER or UPDATE of "CONTADOR".
i have this, but return muting error.
create or replace TRIGGER TRIGGER2
AFTER INSERT OR UPDATE OF CONTADOR ON LOG_ALARMA
for each row
BEGIN
UPDATE LOG_ALARMA a
SET a.DATAMARKER=(SYSDATE);
END;
I look another examples and they work but i can't execute this correctly.
IF i comment the line for each row in my trigger body then it is working fine but it UPDATES all the rows in my table.
You do not issue an update SQL statement, because that would again cause the trigger to fire.
Instead, you just set the value :new.DATAMARKER to sysdate, using PL/SQL not SQL.
Make it a BEFORE INSERT OR UPDATE also.
CREATE TABLE Mutating
(
ID1 NUMBER,
DATE1 DATE
)
data present in a table is
ID1 DATE1
1 09/01/2015 14:09:14
1 08/31/2015 14:09:21
2 08/30/2015 14:09:30
Now i want to update the date1 if any update happens on id1 column in that situation i have used trigger look below.
CREATE OR REPLACE TRIGGER Mutating_trg
before INSERT OR DELETE OR UPDATE ON Mutating
referencing old as old new as new
for each row
begin
if updating then
:new.date1:=sysdate;
end if;
end;
then i have issued update statement
update set Mutating id1=6 where trunc(date1)=trunc(sysdate-2)
1 row updated
Now look into results
ID1 DATE1
1 09/01/2015 14:09:14
6 09/02/2015 14:09:14
2 08/30/2015 14:09:30
You should use before statement.

autonomous_transaction in trigger and constraint violation

I've encountered an interesting situation experimenting with the autonomous_transaction. Consider the following situation (please note it's is not intended to be written this way: just proof of concept):
create table t
(
id int primary key,
changed date
)
/
create or replace trigger t_trig
before insert or update
on t
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin
:new.changed := sysdate;
commit;
end;
/
insert into t(id, changed) values (1, sysdate);
insert into t(id, changed) values (2, sysdate);
The changed date as of current time:
SQL> select * from t;
ID CHANGED
--------- -----------------
1 19.09.11 15:29:44
2 19.09.11 15:32:35
Let's take a 5 sec break and then do the following:
update t set id = 2 where id = 1;
Obviously it will fail with constraint violation, but it doesn't change the changed attribute as well:
SQL> select * from t;
ID CHANGED
--------- -----------------
1 19.09.11 15:29:44
2 19.09.11 15:32:35
My question is: why is this happening? I'm sure I misunderstand some basic concepts but I can't get the idea.
Thanks in advance for your help.
The PRAGMA AUTONOMOUS TRANSACTION saves the context, opens another session and makes something. Commit is a must, because otherwise the changes will be lost. You can understand that only the changes made in some block in the database makes sense in this session (autonomous).
So, in your trigger you do nothing.
That variable, :new.changed is "changed" in another session, if we can say it in this mode. It is not changed for your update.

Resources