trigger in oracle allows insert if field is found - oracle

I have three tables called products, customers and reserve_products. The trigger is on the reserve products table and will only let you insert into the table if the customer id and product id and quantity of product is found and not 0. I am having problems with the sql and checking if the rows exist.
My tables are as follows:
products
CREATE TABLE "SNAGEL"."PRODUCTS"
( "PID" NUMBER(6,0),
"PRODUCT_NAME" VARCHAR2(255 BYTE),
"QUANTITY" NUMBER,
"PRICE" NUMBER,
"PRODUCT_SIZE" VARCHAR2(255 BYTE),
"PRODUCT_VALUE" NUMBER,
PRIMARY KEY ("PID")
Customers
CREATE TABLE "SNAGEL"."CUSTOMERS"
( "CID" NUMBER(6,0),
"FIRST_NAME" VARCHAR2(30 BYTE),
"LAST_NAME" VARCHAR2(30 BYTE),
"PHONE_NUMBER" NUMBER,
PRIMARY KEY ("CID")
Reserve Product
my trigger
CREATE OR REPLACE TRIGGER reserveProductTrigger
before insert on RESERVE_PRODUCT FOR EACH ROW
DECLARE
pid products.pid%rowtype;
pidError exception;
cid customers.cid%rowtype;
cidError exception;
pidSoldout exception;
productQuantity number;
BEGIN
select cid from CUSTOMERS
where cid = :new.CID;
select COUNT(pid from products
where pid = :new.PID;
select quantity into productQuantity
from products
where pid= :new.pid;
if cid = null then
raise cidError;
elsif pid = null then
raise pidError;
elsif productQuantity = 0 then
raise pidSoldout;
else
update products
set quantity = productQuantity -1
where pid = pid;
END IF;
EXCEPTION
When cidError then
DBMS_OUTPUT.PUT_LINE('CUSTOMER not found error!');
RAISE;
When PidError then
DBMS_OUTPUT.PUT_LINE('PRODUCT not found error!');
RAISE;
When pidSoldout then
DBMS_OUTPUT.PUT_LINE('product sold out error!');
RAISE;
END;
/

You forgot to store the details from your select statements into the variables yoiu created for them. So try this instead:
CREATE OR REPLACE TRIGGER reserveProductTrigger
before insert on RESERVE_PRODUCT FOR EACH ROW
DECLARE
pid products.pid%type;
pidError exception;
cid customers.cid%type;
cidError exception;
pidSoldout exception;
productQuantity number;
BEGIN
BEGIN
select cid into CID
from CUSTOMERS
where cid = :new.CID;
EXCEPTION WHEN NO_DATA_FOUND THEN
raise cidError;
END;
BEGIN
select pid
, quantity
INTO PID
, productQuantity
from products
where pid = :new.PID;
EXCEPTION WHEN NO_DATA_FOUND THEN
raise pidError;
END;
if productQuantity <= 0 then -- If it goes negative you've over
raise pidSoldout; -- committed and shouldn't accept either.
else
update products
set quantity = productQuantity -1
where pid = pid;
END IF;
EXCEPTION
When cidError then
DBMS_OUTPUT.PUT_LINE('CUSTOMER not found error!');
RAISE;
When PidError then
DBMS_OUTPUT.PUT_LINE('PRODUCT not found error!');
RAISE;
When pidSoldout then
DBMS_OUTPUT.PUT_LINE('product sold out error!');
RAISE;
END;
/
However, rather than relying on the trigger to check on the existence of PID and CID, you should use referential integrity constraints. Additionally you can put a check constraint on the Quantity column in Products requiring it to be >= 0.
Then the only thing you need to do in your trigger is decrement the Quantity:
CREATE TABLE "PRODUCTS"
( "PID" NUMBER(6,0),
"PRODUCT_NAME" VARCHAR2(255 BYTE),
"QUANTITY" NUMBER CONSTRAINT PRODUCT_MIN_QUANTITY CHECK (QUANTITY >= 0), -- Add Check constraint here
"PRICE" NUMBER,
"PRODUCT_SIZE" VARCHAR2(255 BYTE),
"PRODUCT_VALUE" NUMBER,
CONSTRAINT PRODUCTS_PK PRIMARY KEY ("PID") -- Name the PK Constraint
);
CREATE TABLE "CUSTOMERS"
( "CID" NUMBER(6,0),
"FIRST_NAME" VARCHAR2(30 BYTE),
"LAST_NAME" VARCHAR2(30 BYTE),
"PHONE_NUMBER" NUMBER,
CONSTRAINT CUSTOMERS_PK PRIMARY KEY ("CID") -- Name the PK Constraint
);
create table reserve_product (
PID NOT NULL CONSTRAINT reserve_product_fk REFERENCES products(PID) -- Use referential integrity here
, CID NOT NULL CONSTRAINT reserve_product_fk REFERENCES customers(CID) -- and here
)
CREATE OR REPLACE TRIGGER reserveProductTrigger
before insert on RESERVE_PRODUCT FOR EACH ROW
BEGIN
update products
set quantity = quantity -1
where pid = :new.pid;
END;
/
Now if either PID or CID is missing from Products or Customers respectivly, or if the Products record has a quantity of 0, appropriate exceptions will be raised.

CREATE OR REPLACE TRIGGER trg_reserve_products
BEFORE INSERT ON reserve_products
FOR EACH ROW
DECLARE
v_count NUMBER;
v_p_id NUMBER(6);
v_c_id NUMBER(6);
v_p_quantity products.p_quantity%TYPE;
v_p_name products.p_name%TYPE;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM products
WHERE p_id = :NEW.p_id;
IF v_count = 0 THEN
raise_application_error(-20000 , 'Invalid Product ID.');
END IF;
SELECT COUNT(*)
INTO v_count
FROM customers
WHERE c_id = :NEW.c_id;
IF v_count IS NULL THEN
raise_application_error(-20000 , 'Invalid Customer ID.');
END IF;
SELECT p_quantity, p_name
INTO v_p_quantity, v_p_name
FROM products
WHERE p_id = :NEW.p_id;
IF v_p_quantity < 1 THEN
raise_application_error(-20000 , v_p_name || ' 4 is currently out of stock.');
END IF;
UPDATE products
SET p_quantity = p_quantity - 1
WHERE p_id = :NEW.p_id;
END trg_reserve_products;
SELECT * FROM products;
INSERT INTO reserve_products VALUES(1, 101, 1);
INSERT INTO reserve_products VALUES(2, 46, 1);
INSERT INTO reserve_products VALUES(3, 46, 2);
INSERT INTO reserve_products VALUES(4, 46, 3);
SELECT * FROM products;
SELECT * FROM reserve_products;

Related

how to validate employee age that it must me greater than 18 before inserting record(tiried using check constraint while creating table but not work)

--dept table
create table department(
dept_id number(5) ,
dept_name varchar2(100),
dept_city varchar2(100) ,
dept_country varchar2(100),
CONSTRAINT dept_pk PRIMARY KEY(dept_id)
);
insert into department( dept_id, dept_name, dept_city, dept_country )values(1,'hr','hyderabad','india');
insert into department( dept_id, dept_name, dept_city, dept_country )values(2,'marketing','banglore','india');
insert into department(dept_id, dept_name, dept_city, dept_country)values(3,'sales','dhaka','bangladesh');
create sequence s1
start with 1
increment by 1;
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_age number(3) ,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee_details
FOR EACH ROW
DECLARE
emp_age number;
BEGIN
IF (employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'ravi',45,7333,1);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'sai',74,4451,2);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'chandu',35,9428,3);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'raju',7,25422,2);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'teja',36,7955,1);
select * from employee
You want to use the :NEW record to get the value from the row being inserted (and to use the EMPLOYEE table rather than EMPLOYEE_DETAILS):
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF (:NEW.employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here
However, you should consider storing date of birth rather than age as tomorrow (or definitely next year) the age value will be outdated but storing the date of birth and calculating the age would not.
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_dob DATE,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF :NEW.employee_dob > TRUNC(ADD_MONTHS(SYSDATE, -18*12)) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here

Need to accept comma separated inputs from the stored procedure and have to process as I have explained in the below body but getting compilation err

CREATE TABLE STAGING
(
E_ID NUMBER(10),
E_NAME VARCHAR2(30),
E_LOC VARCHAR2(30),
VALIDATION_STATUS varchar2(30),
validation_msg varchar2(30),
req_id number(10)
);
INSERT INTO staging VALUES(1, 'A', 'AA', NULL, NULL, 1);
INSERT INTO staging VALUES(2, 'B', 'BB', NULL, NULL, 1);
INSERT INTO staging VALUES(3, 'C', 'CC', NULL, NULL, 1);
INSERT INTO staging VALUES(NULL, 'D', 'DD', NULL, NULL, 2);
INSERT INTO staging VALUES(NULL, 'E', 'EE', NULL, NULL, 2);
INSERT INTO staging VALUES(NULL, 'F', 'GG', NULL, NULL, 2);
CREATE TABLE tab_ref
(
ref_id number(10),
ref_name varchar2(30)
);
INSERT INTO tab_ref VALUES(1, 'aa');
INSERT INTO tab_ref VALUES(2, 'bb');
INSERT INTO tab_ref VALUES(3, 'cc');
INSERT INTO tab_ref VALUES(4, 'dd');
CREATE TABLE tab_ref_2
(
ref_id number(10),
ref_name varchar2(30)
);
INSERT INTO tab_ref_2 VALUES(1, 'ee');
INSERT INTO tab_ref_2 VALUES(2, 'ff');
INSERT INTO tab_ref_2 VALUES(3, 'gg');
INSERT INTO tab_ref_2 VALUES(4, 'hh');
CREATE TABLE SUMMARY_TAB
(
TOT_RECORDS NUMBER(10,0),
SUCCESS_RECORDS NUMBER(10,0),
FAILED_RECORDS NUMBER(10,0),
process_status varchar2(30)
);
CREATE TABLE TARGET_TAB
(
E_ID NUMBER(10,0),
E_NAME VARCHAR2(30),
E_LOC VARCHAR2(30)
);
Stored procedure:
create or replace procedure sp_stage_target(iv_req_id IN sys.OdciNumberList,ov_err_msg OUT varchar2) is
lv_succ_rec number(30);
lv_fail_rec number(30);
lv_count_ref number(10);
lv_count_ref2 number(10);
lv_threshold_cnt number(10);
lv_RejectedCount number(10);
lv_status varchar2(30);
begin
lv_succ_rec := 0;
lv_fail_rec := 0;
lv_threshold_cnt := 5;
/*First checking whether data is present in reference table or not.
If data is not present then process should stop*/
select count(1) into lv_count_ref from tab_ref;
select count(1) into lv_count_ref2 from tab_ref_2;
if lv_count_ref = 0 then
ov_err_msg := 'Records are not present in the reference table !!Cannot proceed';
elsif lv_count_ref2 = 0 then
ov_err_msg := 'Records are not present in the reference table !!Cannot proceed';
else
dbms_output.put_line('Data are present into reference tables');
merge into staging d
using (
select 'Fail' as validation_status, t.column_value as req_id
from table(iv_req_id) t
) s
on (d.req_id = s.req_id)
when matched then
update set
d.validation_status = s.validation_status
, d.validation_msg = case
when e_id is null then 'Id is not present'
else 'Id is longer than expected'
end
where e_id is null OR LENGTH(e_id) > 4;
lv_RejectedCount := SQL%ROWCOUNT;
end if;
--If rejected count is less than lv_threshold_cnt i.e 5
--then success records will go in target_tab and failed records will go in reject_tab
if lv_RejectedCount <= lv_threshold_cnt then
lv_status := 'Success';
dbms_output.put_line('Success');
merge into target_tab t
using (
select e_id, e_name, e_loc
from staging
where validation_status is null and req_id in (select column_value from table(iv_req_id))
) s
on (t.e_id = s.e_id)
when matched then
update set
t.e_name = s.e_name,
t.e_loc = s.e_loc
when not matched then
insert (t.e_id,t.e_name,t.e_loc)
values (s.e_id,s.e_name,s.e_loc);
lv_succ_rec := SQL%ROWCOUNT;
end if;
insert into reject_tab
select e_id, e_name, e_loc, validation_status,validation_msg
from staging
where validation_status = 'Fail' and req_id in (select column_value from table(iv_req_id));
lv_fail_rec := SQL%ROWCOUNT;
--In Summary table keeping track of all the records i.e success record, failed records
dbms_output.put_line('Inserting into Summary table');
insert into summary_tab(tot_records, success_records, failed_records, process_status)
values (lv_succ_rec + lv_fail_rec, lv_succ_rec, lv_fail_rec, lv_status);
ov_err_msg := 'Procedure completed succesfully';
end;
Calling Procedure:
set serveroutput on;
declare
err_msg;
begin
sp_main_target(sys.OdciNumberList(1,2),err_msg);
dbms_output.put_line(err_msg);
end;
Getting compilation error and also I am not not how to process for individually for each request_id and process so have highlighted the requirement in comment block.
Error :
I wanted to create a stored procedure that will handle all the below points and I have tried but not been able to get the results.
The stored procedure should accept multiple input parameters while calling a procedure and should process for every request-id given in the parameter with comma-separated.
Then stored procedure will check whether data is present or not in the ref table (tab_ref & tab_ref_2). If data is present then only the process should start otherwise it will not proceed further.
Then it will check the data in the staging table and will do validation for e_id is null or not or its length should not exceed the given limit. For every failed validation it will update the validation status and validation msg column and have to keep count of rejected columns.
After validation, if the threshold count is less then the lv_threshold_count then insertion will happen in both the tables with status as 'Success 'i.e target table and rejected table with validation_status as null and Fail respectively.
If threshold count is more than the lv_threshold_count then it will insert into the rejected table with status as 'Fail'.
Then at last it will show all the records count into the summary table.
You start an IF on line 20 of your procedure, but you don't have a corresponding END IF.
if lv_count_ref = 0 then

SQL Developer will not insert data

create or replace PROCEDURE ADD_TO_BLACKLIST(
P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
DECLARE
E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO E_COUNT FROM EXAMPLE_TABLE
WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL)=CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com');
ELSE
UPDATE EXAMPLE_TABLE
SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) =
(SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL) = CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
COMMIT;
END IF;
OPEN T_CURSOR For
SELECT * FROM EXAMPLE_VIEW
WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END;
END ADD_TO_BLACKLIST;
This compiles, but when I try to test it with a valid P_EMPLOYEE_USERNAME (which I've confirmed to be in the EXAMPLE_VIEW), I do not see any data being inserted.
I am new to PLSQL and not sure how to figure out the value of E_COUNT
The Example_Table DDL is
CREATE TABLE "Example_Table"
( "EMPLOYEE_NUMBER" NUMBER NOT NULL ENABLE,
"EMPLOYEE_USERNAME" VARCHAR2(250 BYTE) NOT NULL ENABLE,
"ACCOUNT_STATUS" NUMBER DEFAULT 0,
"ACCOUNT_STATUS_LAST_UPDATE" TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE,
CONSTRAINT "BOE_SAFEGAURD_PK" PRIMARY KEY ("EMPLOYEE_USERNAME"))
The issue is in below line,you are not converting the case after concatenation.please modify and try below,
WHERE UPPER(EMAIL) = UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
EDIT : To prove the theory please find below the details.
I have tested this and it works,
DDL to create the tables:
CREATE TABLE Example_Table
( EMPLOYEE_NUMBER NUMBER NOT NULL ENABLE,
EMPLOYEE_USERNAME VARCHAR2(250 BYTE) NOT NULL ENABLE,
ACCOUNT_STATUS NUMBER DEFAULT 0,
ACCOUNT_STATUS_LAST_UPDATE TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE,
CONSTRAINT BOE_SAFEGAURD_PK PRIMARY KEY (EMPLOYEE_USERNAME));
CREATE TABLE Example_view
( EMPLOYEE_NUMBER NUMBER NOT NULL ENABLE,
EMAIL VARCHAR2(250 BYTE) NOT NULL ENABLE,
ACCOUNT_STATUS NUMBER DEFAULT 0,
ACCOUNT_STATUS_LAST_UPDATE TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE
);
DML to populate data to example_view that will be used for the test.
insert into example_view values(1,'Test#microsoft.com',1,sysdate);
Modified the procedure to add UPPER on the rightside of the join for both insert and update conditions and place the commit after end if.A good code should have only one commit and that should be at the end of execution before the exception block of main begin..end block.
create or replace PROCEDURE ADD_TO_BLACKLIST(
P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
DECLARE E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO E_COUNT FROM EXAMPLE_TABLE WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW WHERE UPPER(EMAIL)=UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
ELSE
UPDATE EXAMPLE_TABLE SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) = (SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW WHERE UPPER(EMAIL)=UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com')));
END IF;
COMMIT;
OPEN T_CURSOR For
SELECT * FROM EXAMPLE_VIEW WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END;
END ADD_TO_BLACKLIST;
In an anonymous block invoked the procedure,
DECLARE
T_CURSOR SYS_REFCURSOR;
BEGIN
ADD_TO_BLACKLIST('test',T_CURSOR);
end;
Ran a query to check if records are inserted,
select * from example_table;
Output is below,
You just need a commit after IF-ELSE condition rather than inside it. I have updated your code along with some other minor updates -
create or replace PROCEDURE ADD_TO_BLACKLIST( P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*)
INTO E_COUNT
FROM EXAMPLE_TABLE
WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL
FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL) = CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com');
ELSE
UPDATE EXAMPLE_TABLE
SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) = (SELECT EMPLOYEE_NUMBER, EMAIL
FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL)=CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
END IF;
COMMIT;
OPEN T_CURSOR For
SELECT *
FROM EXAMPLE_VIEW
WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END ADD_TO_BLACKLIST;

PL/SQL Trigger to calculate total attendance

I want to create a trigger to calculate total attendance for each distinct record from the given table.
DAILY_ATT
The second table should generate output like this:
TOTAL_ATT
Here's what I have done:
CREATE TABLE daily_att
(
roll_no NUMBER(5),
subject VARCHAR2(10),
attendance NUMBER(5),
date_att DATE
);
CREATE TABLE total_att
(
roll_no NUMBER(5) NOT NULL PRIMARY KEY,
total_attendance NUMBER(5)
);
INSERT INTO DAILY_ATT VALUES(1, 'MATHS', 0, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(1, 'ENG', 1, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'MATHS', 1, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'ENG', 1, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(1, 'MATHS', 1, '05-MAY-18');
INSERT INTO DAILY_ATT VALUES(1, 'ENG', 1, '05-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'MATHS', 0, '05-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'ENG', 0, '05-MAY-18');
SELECT * FROM DAILY_ATT;
CREATE OR replace TRIGGER att
AFTER INSERT OR UPDATE ON daily_att
FOR EACH ROW
BEGIN
SELECT SUM(attendance)
INTO Total_att(total_attendance)
FROM daily_att
WHERE roll_no = :NEW.roll_no;
END;
Hope this will serve your purpose
CREATE OR REPLACE TRIGGER create_subtotal
AFTER INSERT OR UPDATE OR DELETE ON DAILY_ATTENDANCE
FOR EACH ROW
DECLARE
V_ROLL_NO NUMBER(8);
V_TOT_ATTENDANCE NUMBER(4);
BEGIN
SELECT ROLL_NO,SUM(ATTENDANCE)
INTO V_ROLL_NO,V_TOT_ATTENDANCE
FROM DAILY_ATTENDANCE
WHERE ROLL_NO = :new.ROLL_NO AND DATE_ATT=TRUNC(SYSDATE);
BEGIN
update TOTAL_ATTENDANCE
set TOTAL_ATTENDANCE= TOT_ATTENDANCE
where ROLL_NO = V_ROLL_NO;
if sql%rowcount = 0 then
-- no rows were updated, so the record does not exist
insert into TOTAL_ATTENDANCE (ROLL_NO,TOTAL_ATTENDANCE )
values ( V_ROLL_NO,V_TOT_ATTENDANCE );
END IF;
END;
END;
I would recommend you to not abuse a Trigger for this purpose. Simply create a view.
CREATE
OR replace VIEW total_att AS
SELECT roll_no
,SUM(attendance) as total_attendance
FROM daily_att
GROUP BY roll_no;
select * FROM total_attendance;
ROLL_NO TOTAL_ATTENDANCE
1 3
2 2
My suggestion would be to use this set of 2 triggers:
The core one:
CREATE OR REPLACE TRIGGER create_subtotal
AFTER UPDATE OF roll_no, attendance OR INSERT OR DELETE ON daily_att
FOR EACH ROW
DECLARE
v_roll_no daily_att.roll_no%TYPE;
v_before daily_att.attendance%TYPE;
v_after daily_att.attendance%TYPE;
v_diff daily_att.attendance%TYPE;
BEGIN
IF UPDATING AND (:NEW.roll_no <> :OLD.roll_no) THEN
RAISE_APPLICATION_ERROR( -20001, 'Altering ROLL_NO is not allowed!' );
END IF;
IF NOT INSERTING THEN
v_before := :OLD.attendance;
v_roll_no := :OLD.roll_no;
ELSE
v_before := 0;
v_roll_no := :NEW.roll_no;
END IF;
IF NOT DELETING THEN
v_after := :NEW.attendance;
ELSE
v_after := 0;
END IF;
v_diff := v_after - v_before;
IF INSERTING OR (v_diff <> 0) THEN
UPDATE total_att
SET total_attendance = total_attendance + v_diff
WHERE roll_no = v_roll_no;
IF SQL%ROWCOUNT = 0 THEN
INSERT INTO total_att (roll_no, total_attendance)
VALUES (v_roll_no, v_diff);
END IF;
END IF;
END;
An auxiliary one:
CREATE OR REPLACE TRIGGER delete_subtotal
AFTER DELETE ON daily_att
BEGIN
DELETE FROM total_att
WHERE NOT EXISTS (SELECT 1
FROM daily_att d
WHERE total_att.roll_no = d.roll_no);
END;
I've assumed that ROLL_NO is never NULL, nor should ever be changed, and that ATTENDANCE is also never NULL. I basically ignored the SUBJECT and DATE_ATT columns, as from the question, they do not seem to impact the goal.
The auxiliary trigger can be dropped, if you do not need to handle deletes from the DAILY_ATT table, or can stand zero entries in TOTAL_ATT table, for things removed from DAILY_ATT completely.
For better performance, the check for ROLL_NO change should be moved to a separate BEFORE UPDATE trigger. The auxiliary trigger will usually benefit much from an index on ROLL_NO in the DAILY_ATT table.
Here's an SQL Fiddle for this.

Assigning data in a new table to an existing foreign key in a for loop

I was wondering if there was a way to assign new data in a table to an existing foreign key.
For example if I use the following loop to assign randomly generated numbers to columns in the customer table, how would I be able to link cust_id, which I have assigned as a foreign key in the Sales table, with new data created each time a new sale is made?
CUSTOMER:
DECLARE
v_cust_id NUMBER(4) NOT NULL := 0000;
v_cust_name VARCHAR2(30);
v_cust_add VARCHAR2(30);
v_phone VARCHAR2(10);
BEGIN
FOR v IN 1 .. 2000 --Loop 2000 times to create data for the 2000 customers in the database.
LOOP
v_cust_id := v_cust_id + 1;
v_cust_name := dbms_random.string('U',5);
v_cust_add := dbms_random.string('A',15);
v_phone := dbms_random.value(1000000,9999999);
INSERT INTO customer (cust_id, cust_name, cust_add, phone)
VALUES (v_cust_id, v_cust_name, v_cust_add, v_phone);
END LOOP;
END;
/
SALES:
DECLARE
v_sale_id NUMBER(4) NOT NULL := ;
v_sale_price NUMBER(8,2);
v_sale_date DATE;
v_no_of_prods NUMBER(4);
v_prod_id NUMBER(4);
v_desp_id NUMBER(4);
v_cust_id NUMBER(4);
BEGIN
FOR v IN 1 .. 10
LOOP
v_sale_id :=
v_sale_price
v_sale_date :=
v_no_of_products :=
v_prod_id :=
v_desp_id :=
v_cust_id :=
INSERT INTO sales (sale_id, sale_price, sale_date, no_of_prods, prod_id, desp_id, cust_id)
VALUES (v_sale_id, v_sale_price, v_sale_date, v_no_of_prods, v_prod_id, v_desp_id, v_cust_id);
END LOOP;
END;
\
You are generating test data to do some kind of performance test?
Let's first generate 2000 customers.
(untested)
insert into customer
(cust_id, cust_name, cust_add, phone)
select
level l,
dbms_random.string('U',5),
dbms_random.string('A',15),
dbms_random.value(1000000,9999999)
from dual
connect by level <= 2000;
Now you can genereate sales data by selecting from the customer table:
insert into sales
(sale_id, sale_price, sale_date, no_of_prods, prod_id, desp_id, cust_id)
select sale_id_sequence.nextval , dbms_random. ...., cust_id
from customer;
insert into sales
(sale_id, sale_price, sale_date, no_of_prods, prod_id, desp_id, cust_id)
select sale_id_sequence.nextval , dbms_random. ...., cust_id
from customer
where mod(cust_id,2) =0;
insert into sales
(sale_id, sale_price, sale_date, no_of_prods, prod_id, desp_id, cust_id)
select sale_id_sequence.nextval , dbms_radom. ...., cust_id
from customer
where mod(cust_id,7) =0;
insert into sales
(sale_id, sale_price, sale_date, no_of_prods, prod_id, desp_id, cust_id)
select sale_id_sequence.nextval , dbms_random. ...., cust_id
from customer
where mod(cust_id,13) =0;
commit;
I assume there is a sequence to create a sale id.
edit1 improvement:
create table customer
( cust_id number(10)
, cust_name varchar2(50)
, cust_add varchar2(30)
, cust_phone varchar2(10)
);
create sequence cust_id_seq;
create table sales
( sale_id number(10)
, prod_no number(10)
, cust_id number(10)
);
create sequence sale_id_seq;
begin
insert into customer
select cust_id_seq.nextval
, dbms_random.string('U',5)
, dbms_random.string('A',15)
, trunc(dbms_random.value(1000000,9999999))
from dual
connect by level < 2000;
for i in 1..10 loop
insert into sales
select sale_id_seq.nextval
, trunc(dbms_random.value(1,100))
, cust_id
from customer;
insert into sales
select sale_id_seq.nextval
, trunc(dbms_random.value(1,100))
, cust_id
from customer
where mod(cust_id+i,2)=0;
insert into sales
select sale_id_seq.nextval
, trunc(dbms_random.value(1,100))
, cust_id
from customer
where mod(cust_id+i,7)=0;
end loop;
end;
/
commit;
select count(*) from customer;
select count(*) from sales;

Resources