I want to create a trigger which enforces condition which claims as details column may be changed only in 3 days after it placed
CREATE TRIGGER BEFORE UPDATE
CREATE TRIGGER INSTEAD OF UPDATE
I have OrderId, Details, OrderDate columns
How can I do that? Can you help, please
Here's an example.
First, test case:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> create table test
2 (orderid number primary key,
3 details varchar2(20),
4 orderdate date
5 );
Table created.
SQL> insert into test (orderid, details, orderdate)
2 select 1, 'test 1', date '2018-10-15' from dual union all
3 select 2, 'test 2', date '2018-10-22' from dual;
2 rows created.
SQL> select sysdate from dual;
SYSDATE
----------
23.10.2018
SQL> select * from test;
ORDERID DETAILS ORDERDATE
---------- -------------------- ----------
1 test 1 15.10.2018
2 test 2 22.10.2018
SQL>
Trigger:
SQL> create or replace trigger trg_bu_test
2 before update of details on test
3 for each row
4 begin
5 if trunc(sysdate) - :new.orderdate > 3 then
6 raise_application_error(-20001, 'More than 3 days have passed; update is not allowed');
7 end if;
8 end;
9 /
Trigger created.
Testing:
SQL> -- ID = 1 - 8 days have passed, no update is allowed
SQL> update test set details = 'xxx' where orderid = 1;
update test set details = 'xxx' where orderid = 1
*
ERROR at line 1:
ORA-20001: More than 3 days have passed; update is not allowed
ORA-06512: at "SCOTT.TRG_BU_TEST", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG_BU_TEST'
SQL> -- ID = 2 - 1 day passed - update is allowed
SQL> update test set details = 'yyy' where orderid = 2;
1 row updated.
SQL>
P.S. INSTEAD OF triggers (you mentioned in your question) are used with views; forget about them (in this case).
Try using AFTER Trigger
create or replace TRIGGER TR_details_update
AFTER UPDATE ON order_det
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
IF UPDATING THEN
IF NVL(:OLD.Details, 'XXX') <> NVL(:NEW.Details, 'XXX') AND :OLD.OrderDate <= TRUNC(SYSDATE) - 3 THEN
update order_det
set Details = :new.Details
where Details = :old.Details
and OrderId = :old.OrderId;
END IF;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR (-20000,'Details must be changed within 3 days of order date');
END TR_details_update;
Related
I wonder is it able to terminate or stop the Insert if it hits the exceptions.
The trigger coding will be:
CREATE OR REPLACE TRIGGER TRG_UPT_SOLVED_RPT
AFTER INSERT ON Payment
FOR EACH ROW
DECLARE
more_one_row EXCEPTION;
v_rowCount number;
BEGIN
SELECT TCOUNT(ReportID) INTO v_rowCount
FROM Report;
IF v_rowCount <= 1 THEN
**Do anything else**
ELSIF v_rowCount > 0 THEN
RAISE more_one_row;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Update table failed. Row to be update is not found.');
WHEN more_one_row THEN
DBMS_OUTPUT.PUT_LINE('Update table failed. Row to be update is more than one.');
END;
/
This is how I understood it; though, it is not clear what UPDATE TABLE in your code represents. Update which table? How? With which values?
Anyway: sample data:
SQL> select * from report order by reportid;
REPORTID NAME
---------- ------
100 name A
100 name B
200 name C
SQL> select * from payment;
no rows selected
Trigger:
SQL> create or replace trigger trg_upt_solved_rpt
2 before insert on payment
3 for each row
4 declare
5 v_rowcount number;
6 begin
7 select count(*)
8 into v_rowcount
9 from report
10 where reportid = :new.reportid;
11
12 if v_rowcount = 0 then
13 raise_application_error(-20000, 'Update table failed. Row to be update is not found.');
14 elsif v_rowcount > 1 then
15 raise_application_error(-20001, 'Update table failed. Row to be update is more than one.');
16 end if;
17 end;
18 /
Trigger created.
Testing:
SQL> insert into payment (reportid) values (100);
insert into payment (reportid) values (100)
*
ERROR at line 1:
ORA-20001: Update table failed. Row to be update is more than one.
ORA-06512: at "SCOTT.TRG_UPT_SOLVED_RPT", line 12
ORA-04088: error during execution of trigger 'SCOTT.TRG_UPT_SOLVED_RPT'
SQL> insert into payment (reportid) values (200);
1 row created.
SQL> insert into payment (reportid) values (300);
insert into payment (reportid) values (300)
*
ERROR at line 1:
ORA-20000: Update table failed. Row to be update is not found.
ORA-06512: at "SCOTT.TRG_UPT_SOLVED_RPT", line 10
ORA-04088: error during execution of trigger 'SCOTT.TRG_UPT_SOLVED_RPT'
SQL> select * from payment;
REPORTID
----------
200
SQL>
I want to add trigger to count number of movies after inserting!
This is the table to store the count value:
CREATE TABLE mov_count
(mcount NUMBER);
and the movie table:
create table movie
(mov_id number primary key,
mov_title varchar(20),
mov_lang varchar(20));
This is the trigger I have created:
create trigger count_movie_trg
after insert on movie
for each row
BEGIN
UPDATE mov_count
SET mcount = (SELECT COUNT(*) FROM movie);
END;
/
After creating this i tried to add movie but its showing mutating trigger/function may not see it error.
It is the FOR EACH ROW that bothers you. It is a table-level trigger, so:
Enter a dummy value for beginning (as you'll update it later):
SQL> insert into mov_count values (0);
1 row created.
Trigger:
SQL> create or replace trigger count_movie_trg
2 after insert on movie
3 begin
4 update mov_count c set
5 c.mcount = (select count(*) from movie m);
6 end;
7 /
Trigger created.
Testing:
SQL> insert into movie
2 select 1, 'Titanic' from dual union all
3 select 2, 'Superman' from dual;
2 rows created.
SQL> select count(*) from mov_count;
COUNT(*)
----------
1
SQL>
Why not just maintain the value without referring to the original table?
create trigger count_movie_trg after insert on movie for each row
begin
update mov_count set mcount = mcount + 1;
end;
To keep the count up-to-date, you'll need a delete trigger as well.
Don't use the table at all; use a view instead.
CREATE VIEW mov_count ( mcount ) AS
SELECT COUNT(*) FROM movie;
db<>fiddle
There is a table with table structure something like this customer_id number(10), listing_id number(12).
Now The data in this table is somewhat above 10 million so i've been given a task of adding a process_id to the table so that the data can be processed in batches in future operations.
so I added a column process_id to the table
alter table temp_lid_cid add process_id number(1) ;
Now i have to add process ids to the customer_ids at random 1 2 3 4 5 6 7, so that they will get processed according to their process_ids when condition where process_id = $1
There are millions of data so i wrote a simple PL
declare
i temp_lid_cid.customer_id%type;
c temp_lid_cid.process_id%type;
begin
c:=0;
for i in (select customer_id from temp_lid_cid)
loop
if (c = 7) then
c := 0;
end if;
c := c+1;
execute immediate q'[insert into temp_lid_cid(process_id) select :var1 as process_id from temp_lid_cid where customer_id = :var2]'using i,c;
end loop;
end;
It throws this error
Error report -
ORA-06550: line 12, column 145:
PLS-00457: expressions have to be of SQL types
ORA-06550: line 12, column 9:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I tried running the insert statement without execute immediate too but it still threw an error.I also tried running the insert statement for a single customer outside the PL and it worked fine.
If you can suggest any other way to do what i'm trying to do without PL that would be great too.
Now The data in this table is somewhat above 10 million so i've been
given a task of adding a process_id to the table so that the data can
be processed in batches
Insert will Insert a new row to your table. You need an Update statement to fullfill your requirement. See below:
DECLARE
i temp_lid_cid.customer_id%TYPE;
c temp_lid_cid.process_id%TYPE;
BEGIN
c := 0;
FOR i IN (SELECT customer_id FROM temp_lid_cid )
LOOP
IF ( c = 7 )
THEN
c := 0;
END IF;
c := c + 1;
UPDATE temp_lid_cid
SET
process_id = c
WHERE customer_id = i.customer_id;
END LOOP;
COMMIT;
END;
Error in your code:
In your Loop, customer_id is fetched using i.customer_id. So in your insert statement replace as below:
using c,i.customer_id;
Suggestion:
Since the number of rows is 10 Million so i would recommend to use BULK Operation to perform Update.
DECLARE
c temp_lid_cid.process_id%TYPE;
type v_cust_id is table of temp_lid_cid.customer_id%TYPE index by pls_integer;
i v_cust_id;
BEGIN
c := 0;
SELECT customer_id
BULK COLLECT INTO i
FROM temp_lid_cid;
FORALL rec IN 1..i.count
UPDATE temp_lid_cid
SET
process_id = c + i(rec) -- Updating Process_Id with Customer_id
WHERE customer_id = i(rec);
COMMIT;
END;
you interchanged your sql type.
declare
i temp_lid_cid.process_id%type;
c temp_lid_cid.customer_id%type;
Why use PL/SQL block and loop for it. It can be done using a single merge statement as following: (I am using the range from 1-4 numbers, you can use 1-7 numbers by replacing 4 with 7 in merge statement)
Oracle table creation:
SQL> CREATE TABLE TEMP_LID_CID (
2 CUSTOMER_ID NUMBER(10),
3 LISTING_ID NUMBER(12),
4 PROCESS_ID NUMBER(1)
5 );
Table created.
Inserting data into the table:
SQL> insert into temp_lid_cid values (1,10,null);
1 row created.
SQL> insert into temp_lid_cid values (1,20,null);
1 row created.
SQL> insert into temp_lid_cid values (1,30,null);
1 row created.
SQL> insert into temp_lid_cid values (1,40,null);
1 row created.
SQL> insert into temp_lid_cid values (1,50,null);
1 row created.
SQL> insert into temp_lid_cid values (2,10,null);
1 row created.
SQL> insert into temp_lid_cid values (2,20,null);
1 row created.
SQL> insert into temp_lid_cid values (2,30,null);
1 row created.
Current view of the data
SQL> select * from TEMP_LID_CID;
CUSTOMER_ID LISTING_ID PROCESS_ID
----------- ---------- ----------
1 10
1 20
1 30
1 40
1 50
2 10
2 20
2 30
8 rows selected.
SQL>
query to achieve the desired result:
SQL> MERGE INTO TEMP_LID_CID T USING (
2 SELECT
3 T1.*,
4 T1.ROWID AS RID,
5 MOD(ROW_NUMBER() OVER(
6 ORDER BY
7 T1.CUSTOMER_ID
8 ), 4) AS RANDOM_PROCESS_ID -- replace 4 with 7
9 FROM
10 TEMP_LID_CID T1
11 )
12 T1 ON ( T.ROWID = T1.RID )
13 WHEN MATCHED THEN UPDATE SET T.PROCESS_ID = DECODE(T1.RANDOM_PROCESS_ID, 0, 4, T1.RANDOM_PROCESS_ID); -- replace 4 with 7
Data after update:
SQL> select * from TEMP_LID_CID;
CUSTOMER_ID LISTING_ID PROCESS_ID
----------- ---------- ----------
1 10 1
1 20 2
1 30 3
1 40 4
1 50 1
2 10 2
2 20 3
2 30 4
8 rows selected.
SQL>
Since process_id can be random, you could also use a simple query like this:
update temp_lid_cid set process_id = mod(rownum,7)+1;
I am trying the create trigger where if one field in my database is null then automatically update another to be null. This is what I have tried:
CREATE TRIGGER t123
BEFORE INSERT ON Customer
REFERENCING NEW ROW AS New
FOR EACH ROW
BEGIN
IF new.f is null
THEN
new.course_year = null;
END IF;
END;
You do not need the REFERENCING, you can simply use :NEW.
Also, outside a WHEN clause, you have to use :NEW and not NEW:
SQL> create table customer ( f varchar2(100), course_year varchar2(100));
Table created.
SQL> CREATE TRIGGER t123
2 BEFORE INSERT OR UPDATE
3 ON Customer
4 FOR EACH ROW
5 BEGIN
6 IF :new.f is null
7 THEN
8 :new.course_year := null;
9 END IF;
10 END;
11 /
Trigger created.
SQL> insert into Customer(f, course_year) values (123, 123);
1 row created.
SQL> update customer set f = null, course_year = 'test';
1 row updated.
SQL> select * from customer;
F COURSE_YEA
---------- ----------
SQL>
I am doing some coursework where I am supposed to create a trigger that checks the stock level before an order is created, to see if there is enough product in stock to meet the order. Problem is the lecturer has not properly taught us the syntax of creating triggers, my issue being I dont know how to tell the trigger which row/collumn in the stocktable I am checking against. do I need to join the tables?
This is the code I have so far (I am just winging it here, it doesnt really work, but I'm putting it here to give you an idea of what it is I want to acchieve):
CREATE OR REPLACE TRIGGER stock_check
BEFORE INSERT OR UPDATE OF product_quantity ON orderline
FOR EACH ROW
DECLARE
Newtotal number;
insufficient_stock exception;
BEGIN
IF(:NEW.product_quantity > product_stock.stock_quantity) then
raise insufficient_stock;
Else
:new.product_stock.stock_quantity - product_quantity = newtotal
Update stock_quantity
Set product_quantity = newtotal
END IF;
exception
when insufficient_stock then
raise_application_error(-20604,'There is not enoguh stock available');
END;
I guess you'll need something like the following:
CREATE OR REPLACE TRIGGER stock_check
BEFORE INSERT ON orderline
FOR EACH ROW
DECLARE
oldtotal NUMBER
newtotal NUMBER;
insufficient_stock EXCEPTION;
BEGIN
SELECT stock_quantity INTO old_total
FROM product_stock
WHERE product_id = :new.product_id:
IF(:new.product_quantity > oldtotal) then
RAISE insufficient_stock;
ELSE
newtotal := oldtotal - :new.product_quantity;
UPDATE stock_quantity
SET product_quantity = newtotal
WHERE product_id = :new.product_id;
END IF;
EXCEPTION
WHEN insufficient_stock THEN
RAISE_APPLICATION_ERROR(-20604,'There is not enoguh stock available');
END;
You still have to add a column for some sort of product_id. And I didn't handle the UPDATE case...
for example:
SQL> create table product_stock (prod_id number, stock_quantity number);
Table created.
SQL> create table orderline (prod_id number, product_quantity number);
Table created.
SQL> insert into product_stock values (1, 1002);
1 row created.
SQL>
SQL> CREATE OR REPLACE TRIGGER stock_check
2 BEFORE INSERT OR UPDATE OF product_quantity ON orderline
3 FOR EACH ROW
4 DECLARE
5 v_stock_quantity product_stock.prod_id%type;
6 v_Newtotal number;
7 insufficient_stock exception;
8 BEGIN
9 if updating
10 then
11 v_Newtotal := :new.product_quantity - :old.product_quantity;
12 else
13 v_Newtotal := :new.product_quantity;
14 end if;
15
16 Update product_stock
17 Set stock_quantity = stock_quantity - v_Newtotal
18 where prod_id = :new.prod_id
19 returning stock_quantity into v_stock_quantity;
20
21 IF (v_stock_quantity < 0)
22 then
23 raise insufficient_stock;
24 END IF;
25 exception
26 when insufficient_stock then
27 raise_application_error(-20604,'There is not enoguh stock available');
28 END;
29 /
Trigger created.
SQL> show errors
No errors.
SQL> select * from product_stock;
PROD_ID STOCK_QUANTITY
---------- --------------
1 1002
SQL> insert into orderline values (1, 1000);
1 row created.
SQL> select * from product_stock;
PROD_ID STOCK_QUANTITY
---------- --------------
1 2
SQL> insert into orderline values (1, 3);
insert into orderline values (1, 3)
*
ERROR at line 1:
ORA-20604: There is not enough stock available
ORA-06512: at "DTD_TRADE.STOCK_CHECK", line 17
ORA-04088: error during execution of trigger 'DTD_TRADE.STOCK_CHECK'
SQL> select * from product_stock;
PROD_ID STOCK_QUANTITY
---------- --------------
1 2
update done first to create a lock on purpose (as if two sessions did this call at once, you want the second to block to avoid stock going under 0).