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>
Related
I want to create a user defined exception for duplicate row insertion i.e an excption is raised when a duplicate data(already present in the table) is inserted into the table.My table is dept with columns deptno,dname,loc.So, i want to raise user defined exception for duplicate entry.
create or replace procedure ADD_DEPT(DEPTNO in dept.deptno%type,
DNAME in dept.dname%type,LOC in dept.loc%type) is
begin
insert into dept values(DEPTNO,DNAME,LOC);
end;
Assuming you have a unique constraint on deptno, or a primary key, as you should be, then you can raise a particular exception using DUP_VAL_ON_INDEX and RAISE_APPLICATION_ERROR
create or replace procedure ADD_DEPT(DEPTNO in dept.deptno%type,
DNAME in dept.dname%type,LOC in dept.loc%type)
is
begin
insert into dept values(DEPTNO,DNAME,LOC);
exception
when dup_val_on_index
then
raise_application_error(-20001,'Value duplicated on deptno' );
end;
UPDATE
Let me show you an example.
SQL> create table x ( c1 number not null primary key , c2 number ) ;
insert into x values ( 1 , '1' );
insert into x values ( 2 , '1' );
Table created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> create or replace procedure add_to_x ( p_c1 in number , p_c2 in number )
2 is
begin
3 4 insert into x values (p_c1 , p_c2);
commit;
5 6 exception when dup_val_on_index
then
7 8 raise_application_error(-20001,'Value duplicated on deptno' );
when others then
9 10 raise;
end; 11
12 /
Procedure created.
SQL> select * from x ;
C1 C2
---------- ----------
1 1
2 1
SQL> exec add_to_x ( 1 , 3 ) ;
BEGIN add_to_x ( 1 , 3 ) ; END;
*
ERROR at line 1:
ORA-20001: Value duplicated on deptno
ORA-06512: at "SYS.ADD_TO_X", line 8
ORA-06512: at line 1
SQL>
I hope you are looking for something like this.
create or replace procedure ADD_DEPT(P_DEPTNO in dept.deptno%type,
P_DNAME in dept.dname%type,P_LOC in dept.loc%type) is
duplicate_value_Exists EXCEPTION;
PRAGMA exception_init( duplicate_value_Exists, -20001 );
v_count number;
begin
select count(1)
into v_count
from dept
WHERE DEPTNO = P_DEPTNO
AND P_DNAME = DNAME
AND LOC = P_LOC;
if v_count >= 1 then
RAISE duplicate_value_Exists;
else
insert into dept values(P_DEPTNO,P_DNAME,P_LOC);
end if;
end;
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 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;
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).