SQL: Trigger Syntax - 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).

Related

Is there a way to terminate the insert query in a trigger?

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>

how to raise user defined exception in PL/SQLwhen duplicate row is inserted into the table.?

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;

PL/SQL insert data into a column with values corresponding to values of another column

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;

How to create update trigger with some condition?

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;

Creating a trigger where if one field is null another automatically is

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>