Trigger that changes value in another table /plsql - oracle

I've created two tables: Employees and Departments
CREATE TABLE EMP
( emp_id number(3) PRIMARY KEY,
dept_id Number(3) NOT NULL,
emp_name Varchar2(50) NOT NULL,
address Varchar2(100),
phone Varchar2(20) NOT NULL,
salary Number(8,2) NOT NULL,
CONSTRAINT fk_DEPT FOREIGN KEY (dept_id) REFERENCES DEPT(DEPT_ID));
CREATE TABLE DEPT
( dept_id number(3) PRIMARY KEY,
dept_name varchar2(50) NOT NULL,
emp_cnt Number(3) NOT NULL)
I need to create trigger that changes value in DEPT.emp_cnt after inserting or deleting data in EMP table.
Here is my attempt
create or replace trigger add_emp_to_the_dep
after insert or delete on EMP
for each row
begin
update DEPT
set emp_cnt = :new.emp_id
where DEPT.dept_id = :new.dept_id;
if INSERTING then
emp_cnt += 1;
else DELETING then
emp_cnt -= 1;
end if;
end;

Wrong syntax; there's no such thing as emp_cnt += 1; in Oracle's PL/SQL.
Try something like this instead:
create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;
/

You can use a compound trigger to collate the changes and make the minimum number of updates:
CREATE TRIGGER add_emp_to_the_dep
FOR INSERT OR UPDATE OR DELETE ON emp
COMPOUND TRIGGER
TYPE ids_type IS TABLE OF EMP.DEPT_ID%TYPE;
TYPE cnt_type IS TABLE OF PLS_INTEGER;
TYPE idx_type IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
ids ids_type := ids_type();
cnts cnt_type := cnt_type();
idxs idx_type := idx_type();
PROCEDURE modify_dept_cnt (
id EMP.DEPT_ID%TYPE,
cnt PLS_INTEGER
)
IS
BEGIN
IF id IS NULL THEN
RETURN;
END IF;
IF NOT idxs.EXISTS(id) THEN
ids.EXTEND;
cnts.EXTEND;
ids(ids.COUNT) := id;
cnts(cnts.COUNT) := cnt;
idxs(id) := ids.COUNT;
ELSE
cnts(idxs(id)) := cnts(idxs(id)) + cnt;
END IF;
END modify_dept_cnt;
AFTER EACH ROW
IS
BEGIN
modify_dept_cnt(:NEW.DEPT_ID, 1);
modify_dept_cnt(:OLD.DEPT_ID, -1);
END AFTER EACH ROW;
AFTER STATEMENT
IS
BEGIN
FORALL i IN 1 .. ids.count
UPDATE dept
SET emp_cnt = emp_cnt + cnts(i)
WHERE dept_id = ids(i);
END AFTER STATEMENT;
END;
/
Then, if you do:
INSERT INTO emp (emp_id, dept_id, emp_name, phone, salary)
SELECT 1, 1, 'Alice', '0', 100 FROM DUAL UNION ALL
SELECT 2, 1, 'Betty', '1', 100 FROM DUAL UNION ALL
SELECT 3, 2, 'Carol', '2', 100 FROM DUAL UNION ALL
SELECT 4, 1, 'Debra', '3', 100 FROM DUAL UNION ALL
SELECT 5, 3, 'Emily', '4', 100 FROM DUAL UNION ALL
SELECT 6, 3, 'Fiona', '5', 100 FROM DUAL;
It will collate all the changes and UPDATE the DEPT table only 3 times, as employees for 3 unique DEPT_ID are added, rather than performing 6 updates, one for each inserted row.
db<>fiddle here

create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;

Related

Trigger DML in oracle database

I have problem with my dml trigger on oracle database.I want to launch trigger when i update first_name or last name on employees table in hr schema. During execution trigger i Have error ORA-00060: Please help. Idon't have any ideas how can i fix it.enter image description here
CREATE OR replace TRIGGER up_sal
BEFORE UPDATE OF first_name, last_name ON employees
FOR EACH ROW
DECLARE
PRAGMA autonomous_transaction;
var_sal employees.salary%TYPE;
var_avg NUMBER;
var_emp_id NUMBER;
BEGIN
SELECT salary
INTO var_sal
FROM employees
WHERE first_name = :NEW.first_name
OR last_name = :NEW.last_name;
SELECT Avg(salary)
INTO var_avg
FROM employees
WHERE department_id IN( :OLD.department_id );
IF var_sal < var_avg THEN
var_emp_id := :OLD.employee_id;
UPDATE employees
SET salary = var_avg * 1.1
WHERE employee_id = var_emp_id;
COMMIT;
END IF;
END;
You can use:
CREATE TRIGGER up_sal
BEFORE UPDATE OF first_name, last_name ON employees
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
var_avg employees.salary%TYPE;
BEGIN
SELECT Avg(salary)
INTO var_avg
FROM employees
WHERE department_id IN( :OLD.department_id );
IF :NEW.salary < var_avg THEN
:NEW.salary := var_avg * 1.1;
END IF;
COMMIT;
END;
/
Which, for the sample data:
CREATE TABLE employees (
department_id NUMBER,
first_name VARCHAR2(20),
last_name VARCHAR2(20),
salary NUMBER
);
INSERT INTO employees (department_id, first_name, last_name, salary)
SELECT 1, 'Alice', 'Abbot', 90 FROM DUAL UNION ALL
SELECT 1, 'Betty', 'Baron', 95 FROM DUAL UNION ALL
SELECT 1, 'Carol', 'Count', 105 FROM DUAL UNION ALL
SELECT 1, 'Debra', 'Duke', 110 FROM DUAL;
Then if you do:
UPDATE employees
SET first_name = TRIM(first_name),
last_name = TRIM(last_name);
Then:
SELECT * FROM employees;
Outputs:
DEPARTMENT_ID
FIRST_NAME
LAST_NAME
SALARY
1
Alice
Abbot
110
1
Betty
Baron
110
1
Carol
Count
105
1
Debra
Duke
110
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

Number of rows UPDATED and INSERTED from the same procedure in PL/SQL

I have a procedure that does the INSERT INTO and then the UPDATE of some fields (both in the same procedure), I'm using this answer from #Clive Number of rows affected by an UPDATE in PL/SQLto know the amount of data that has been updated to put in a log, but it brings me the total number of rows instead of just the records that have been updated.
Is that the right way to know?
What I need is to know how many rows were INSERTED from the INSERT STATEMENT and how many rows were UPDATED from the UPDATE STATEMENT.
My query:
CREATE OR REPLACE PROCEDURE OWNER.TABLE_NAME
AS
-- VARIABLE
v_qtd_regs number := 0;
v_code number;
v_errm VARCHAR2(500);
start_time pls_integer;
end_time pls_integer;
elapse_time number;
proc_name varchar2(100);
i NUMBER;
BEGIN
proc_name := 'PRDWBI_CGA_D_COLUMNS';
start_time := dbms_utility.get_time;
DS_FUNCESP.PRDSBI_GRAVA_LOG( 'I', 'DataWarehouse', proc_name, 'Início Carga' );
-- INSERT INTO TABLE:
INSERT INTO OWNER.TABLE_NAME
(COLUMN_ID, COLUMNS_NAME, COLUMN_NAME2)
(SELECT 1 AS COLUMN_ID, 'TEST' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL);
COMMIT;
-- UPDATE SOME COLUMNS I NEED
UPDATE OWNER.TABLE_NAME y
SET (y.COLUMNS_NAME, y.COLUMN_NAME2) =
(SELECT 'TEST2' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL x WHERE x.COLUMN_ID = y.COLUMN_ID)
WHERE EXISTS (SELECT 'TEST2' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL x WHERE x.COLUMN_ID = y.COLUMN_ID);
-- TO KNOW HOW MANY ROWS WERE UPDATED
i := SQL%rowcount;
COMMIT;
--dbms_output.Put_line(i);
SELECT COUNT(1) INTO v_qtd_regs FROM OWNER.TABLE_NAME where LinData >= TRUNC(SYSDATE);
end_time := dbms_utility.get_time;
elapse_time := ((end_time - start_time)/100);
v_errm := SUBSTR(SQLERRM, 1 , 500);
DS_FUNCESP.PRDSBI_GRAVA_LOG('T', 'DataWarehouse', proc_name, v_errm, v_qtd_regs, elapse_time );
COMMIT;
EXCEPTION
WHEN OTHERS THEN
v_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1 , 500);
DS_FUNCESP.PRDSBI_GRAVA_LOG('E', 'Error', proc_name, v_errm);
END;
QUESTION EDITED TO SHOW A REAL EXAMPLE:
I created a table that takes data from "SYS.DBA_TAB_COLUMNS" just to use as an example, as shown below:
CREATE TABLE "DW_FUNCESP"."D_TEST"
(
"ID_COLUMN" NUMBER(10,0) GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1
START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
"NM_OWNER" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"NM_TABLE" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"CD_COLUMN" NUMBER(20,0) NOT NULL ENABLE ,
"NM_COLUMN" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"DS_COLUMN" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"LINDATE" DATE DEFAULT SYSDATE NOT NULL ENABLE ,
"LINORIGIN" VARCHAR2(100 CHAR) NOT NULL ENABLE
)
Then I created a procedure to identify the inserted and updated records, as below:
CREATE OR REPLACE PROCEDURE DW_FUNCESP.PRDWBI_CGA_D_TEST
AS
-- variaveis de suporte as informações que deve gravar
rows_inserted integer;
rows_updated integer;
BEGIN
-- Insert Into table
INSERT INTO DW_Funcesp.D_TEST
(NM_OWNER, NM_TABLE, CD_COLUMN, NM_COLUMN, DS_COLUMN, LINDATE, LINORIGIN)
(SELECT
NVL(x.NM_OWNER ,'NOT FOUND') AS NM_OWNER ,
NVL(x.NM_TABLE ,'NOT FOUND') AS NM_TABLE ,
NVL(x.CD_COLUMN ,-1) AS CD_COLUMN ,
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE ,
'SYS.DBA_TAB_COLUMNS' AS LINORIGIN
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
LEFT JOIN DW_FUNCESP.D_TEST y
ON y.NM_OWNER = x.NM_OWNER
AND y.NM_TABLE = x.NM_TABLE
AND y.NM_COLUMN = x.NM_COLUMN
WHERE y.ID_COLUMN IS NULL);
rows_inserted := sql%rowcount;
-- Update the table
UPDATE DW_FUNCESP.D_TEST z
SET (z.NM_COLUMN, z.DS_COLUMN, z.LINDATE) =
(SELECT
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
WHERE z.NM_OWNER = x.NM_OWNER
AND z.NM_TABLE = x.NM_TABLE
AND z.CD_COLUMN = x.CD_COLUMN)
WHERE EXISTS (SELECT
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
WHERE z.NM_OWNER = x.NM_OWNER
AND z.NM_TABLE = x.NM_TABLE
AND z.CD_COLUMN = x.CD_COLUMN);
rows_updated := sql%rowcount;
dbms_output.Put_line('inserted=>' || to_char(rows_inserted) || ', updated=>' || to_char(rows_updated));
COMMIT;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END;
So my first insert output was:
inserted=>2821, updated=>2821
So I chose a data to be changed and it was updated, I made the following select to choose which data should be updated to bring in the DBMS output again:
SELECT * FROM DW_FUNCESP.D_TEST WHERE NM_TABLE = 'D_TEST';
I commented in a column as shown in the image, to bring in the update:
COMMENT ON COLUMN DW_FUNCESP.D_TEST.LINORIGIN IS 'The origin of the data';
I ran the procedure again, and the output was:
inserted=>0, updated=>2821
The result for that update:
Shouldn't you have brought just 1 updated data in the output, as only 1 updated? And not all the rows?
e.g.: inserted=>0, updated=>1
So my question remains, am I asking it correctly? Is it possible to obtain this result in the same procedure? Is it the update that is incorrect (despite having updated the data)?
You are not getting the rows inserted and rows updated. SQL%rowcount contains ONLY the number rows from the last select or DML statement. Since you set your variable only after the Update your only get the number of updates. If you want both then you need a separate variable for each.
Hint: There is no need to commit after each DML, actually that is ofter considered a very poor practice. You need to study as bit on transactions. The basic idea being that all operations complete successfully or none of them complete successfully. Look up ATOMIC and Atomicity.
So your revised procedure becomes:
create or replace procedure owner.table_name
as
-- VARIABLE
v_qtd_regs number := 0;
v_code number;
v_errm varchar2(500);
start_time pls_integer;
end_time pls_integer;
elapse_time number;
proc_name varchar2(100);
rows_inserted integer;
rows_updated integer;
begin
proc_name := 'PRDWBI_CGA_D_COLUMNS';
start_time := dbms_utility.get_time;
ds_funcesp.prdsbi_grava_log( 'I', 'DataWarehouse', proc_name, 'Início Carga' );
insert into owner.table_name
(column_id, columns_name, column_name2)
(select 1 as column_id, 'TEST' as column_name, sysdate as column_name2 from dual);
rows_inserted := sql%rowcount;
update owner.table_name y
set (y.columns_name, y.column_name2) =
(select 'TEST2' as column_name, sysdate as column_name2 from dual x where x.column_id = y.column_id)
where exists (select 'TEST2' as column_name, sysdate as column_name2 from dual x where x.column_id = y.column_id);
rows_updated := sql%rowcount;
dbms_output.Put_line('inserted=>' || to_char(rows_inserted) || ', updated=>' || tp_char(rows_updated));
select count(1) into v_qtd_regs from owner.table_name where lindata >= trunc(sysdate);
end_time := dbms_utility.get_time;
elapse_time := ((end_time - start_time)/100);
v_errm := substr(sqlerrm, 1 , 500);
ds_funcesp.prdsbi_grava_log('T', 'DataWarehouse', proc_name, v_errm, v_qtd_regs, elapse_time );
commit;
exception
when others then
v_code := sqlcode;
v_errm := substr(sqlerrm, 1 , 500);
ds_funcesp.prdsbi_grava_log('E', 'Error', proc_name, v_errm);
end;
Try to add the instruction i := SQL%rowcount; after each DML:
after INSERT to have the number of inserted rows
after UPDATE to have the number of updated rows
I would use ORA_SCN as the other answers suggest if you are interested what rows have been inserted or updated. But you want only to know how many, so I would leave the counting to Oracle (might be timeconsuming for larger number of rows).
Please have a look at the data dictionary view USER_TAB_MODIFICATIONS (or ALL_TAB_MODIFICATIONS if the table is in another schema than the procedure.
CREATE TABLE d (
id NUMBER GENERATED ALWAYS AS IDENTITY,
dt DATE DEFAULT SYSDATE,
foo VARCHAR2(128 BYTE)
);
Gathering the table statistics will reset the modifications view:
EXEC DBMS_STATS.GATHER_TABLE_STATS(NULL,'D');
Now after your INSERT, the modifications view will have the number of inserted rows:
INSERT INTO d(foo) SELECT object_name FROM all_objects;
67,141 rows inserted.
SELECT inserts, updates, deletes FROM user_tab_modifications WHERE table_name='D';
INSERTS UPDATES DELETES
67141 0 0
Likewise, after the UPDATE, the updated rows:
UPDATE d SET foo=lower(foo),dt=SYSDATE WHERE mod(id,10)=0;
6,714 rows updated.
SELECT inserts, updates, deletes FROM user_tab_modifications WHERE table_name='D';
INSERTS UPDATES DELETES
67141 6714 0
For clarity, I've used SQL instead of PL/SQL. You might have to grant some special privs to the schema containing the procedure. Add a comment with my name if you run into problems with that.

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