this is a follow up question from my previous post:
PL/SQL Triggers with aggregate function
I have a trigger named updategpa that updates the gpa inside the student table whenever grades get changed in the course table.
This trigger gets called as a result of 'updateGrades' trigger that updates the course table when assignments table receives new data.
Problem: gpa is 0 before and after the trigger is called?
Could anyone say what is wrong with my trigger?
drop table courses;
drop table student;
drop table assignments;
create table student (sid integer, sname char(10), saddress char(10), gpa integer);
create table courses (sid integer, cid integer, cgrade integer);
create table assignments ( sid integer, cid integer, aid integer, agrade integer);
insert into student (sid, sname, saddress, gpa) values (1, 'Mike', 'Brighton', 0);
insert into courses (sid, cid, cgrade) values(1, 2000, 0);
insert into assignments values(1, 2000, 1, 70);
insert into assignments values(1, 2000, 2, 80);
create or replace trigger updateGrades before insert or update or delete of agrade on assignments for each row
begin
if inserting then
update courses set cgrade = :new.agrade where cid = :new.cid and sid = :new.sid;
elsif updating then
update courses set cgrade = :old.agrade + (:new.agrade - :old.agrade)
where cid = :new.cid and sid = :new.sid;
elsif deleting then
delete from courses where cid = :new.cid and sid = :new.sid;
end if;
end;
/
show errors;
create or replace trigger updategpa after insert or update or delete of cgrade on courses for each row
begin
if inserting then
update student set gpa =
(select avg(cgrade) from courses inner join student on courses.sid = student.sid
)
where sid = 1;
elsif inserting then
update student set gpa =
(select avg(cgrade) from courses inner join student on courses.sid = student.sid
)
where sid = 1;
elsif deleting then
delete from student where sid = :new.sid;
end if;
end;
/
show errors;
select * from courses;
select * from student;
select * from assignments;
update assignments set agrade = agrade + 5;
select * from courses;
select * from student;
select * from assignments;
console output:
SQL> select * from student;
SID SNAME SADDRESS GPA
---------- ---------- ---------- ----------
1 Mike Brighton 0
SQL> select * from assignments;
SID CID AID AGRADE
---------- ---------- ---------- ----------
1 2000 1 70
1 2000 2 80
SQL>
SQL> update assignments set agrade = agrade + 5;
2 rows updated.
SQL>
SQL> select * from courses;
SID CID CGRADE
---------- ---------- ----------
1 2000 85
SQL> select * from student;
SID SNAME SADDRESS GPA
---------- ---------- ---------- ----------
1 Mike Brighton 0
SQL> select * from assignments;
SID CID AID AGRADE
---------- ---------- ---------- ----------
1 2000 1 75
1 2000 2 85
The root of the problem is your updategpa trigger needs to track every change to COURSES at the row level, then aggregate those changes to maintain a running total for each student. This is a good use case for a compound trigger. Find out more.
create or replace trigger updategpa
for update or update or insert on courses compound trigger
type cid_nt is table of courses%rowtype;
upd_recs cid_nt;
before statement is
begin
upd_recs := cid_nt();
end before statement;
before each row is
begin
upd_recs.extend();
if inserting or updating then
upd_recs(upd_recs.count()).sid := :new.sid;
else
upd_recs(upd_recs.count()).sid := :old.sid;
end if;
end before each row;
after each row is
begin
null;
end after each row;
after statement is
begin
for idx in 1 .. upd_recs.count() loop
update student
set gpa = (select avg(cgrade)
from courses
where courses.sid = student.sid )
where sid = upd_recs(idx).sid;
end loop;
end after statement;
end;
Here is a LiveSQL demo (free OTN account required).
Related
Structure of two tables in the database are as below.
1) Department table:
Dept_Id number(5) primary key,
Sept_Name varchar2(20),
Employee_strength number(4) not null.
2)Employee table:
E_Id number(5),
E_Name varchar2(20),
Designation varchar2(20),
D_ID number(5) references Department table Dept_ID.
A pl/sql program block to print the name the departments which has employees having the designation as "SE" is to be written and if no record in the department table fulfilling the given conditions found ,code should print the message "No record found" and if the record found code has to print Department name.
Please Help.
Here's one option (based on tables similar to yours; these belong to Scott).
I'll search for a SALESMAN.
SQL> break on deptno
SQL> select distinct d.deptno, e.job
2 from dept d left join emp e on e.deptno = d.deptno
3 order by d.deptno, e.job;
DEPTNO JOB
---------- ---------
10 CLERK
MANAGER
PRESIDENT
20 ANALYST
CLERK
MANAGER
30 CLERK
MANAGER
SALESMAN --> only department 30 has SALESMEN
40
10 rows selected.
SQL>
PL/SQL block:
SQL> set serveroutput on
SQL> declare
2 l_exists number(1);
3 begin
4 for cur_d in (select d.deptno, d.dname from dept d order by d.deptno) loop
5 select max(1)
6 into l_exists
7 from emp e
8 where e.deptno = cur_d.deptno
9 and e.job = 'SALESMAN';
10
11 dbms_output.put_line(cur_d.deptno || ' - ' ||
12 case when l_exists = 1 then cur_d.dname
13 else 'no record found'
14 end);
15 end loop;
16 end;
17 /
10 - no record found
20 - no record found
30 - SALES
40 - no record found
PL/SQL procedure successfully completed.
SQL>
If you want distinct department names then use the distinct() function in the SQL query.
I am using inner join to query the two table based on they have the same department id and department name is "SE".
Here are the steps to be followed:
declare the block(anonymous in this case)
write the SQL query for the cursor
begin the block
open the cursor
fetch the rows from the cursor
print the result
close the cursor
if the cursor doesn't contain any rows, so check for this condition
close the block
DECLARE
CURSOR C IS SELECT distinct(D.DEPT_NAME) FROM DEPARTMENTS D INNER JOIN EMPLOYEES E ON D.DEPT_ID = E.D_ID AND E.DESIGNATION LIKE 'SE';
RES C%ROWTYPE;
BEGIN
OPEN C;
loop
FETCH C INTO RES;
EXIT WHEN C%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(RES.DEPT_NAME);
end loop;
IF(C%ROWCOUNT=0) then
dbms_output.put_line('No record found');
end if;
END;
/
I have a DB trigger before insert on emp table. I would like to add sal, comm from emp_test table and want to use those value as a default in the emp table, by trigger. Any idea how to do it?
Until you provide answers to questions I posted in a comment, here's how you might do it. See if you can adjust it.
EMP_TEST table contains only one row (which is kind of stupid; you'd rather use DEFAULT value for those columns in the EMP table).
SQL> create table emp_test (sal number, comm number);
Table created.
SQL> insert into emp_test (sal, comm) values (3000, 100);
1 row created.
Trigger takes SAL and COMM values if they are provided; otherwise, it takes values from the EMP_TEST table.
SQL> create or replace trigger trg_bi_emp
2 before insert on emp
3 for each row
4 begin
5 select nvl(:new.sal, t.sal),
6 nvl(:new.comm, t.comm)
7 into :new.sal,
8 :new.comm
9 from emp_test t;
10 end;
11 /
Trigger created.
Testing: I didn't provide SAL value (so trigger will insert EMP_TEST one), but I did provide COMM:
SQL> insert into emp (empno, ename, deptno, sal, comm)
2 values (1, 'Littlefoot', 40, null, 50);
1 row created.
SQL> select * from emp where empno = 1;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
1 Littlefoot 3000 50 40
SQL>
I have a procedure which should return 3 dept names and update the start time for these three dept names. This script will be run by parallel threads.
I tried to achieve it using cursors but not returning the result.
Table with inserts:
create table dept (sno number(4), deptname varchar2(40), start_time date)
insert into dept values(1,'DEPT1',NULL);
insert into dept values(1,'DEPT2',NULL);
insert into dept values(1,'DEPT3',NULL);
insert into dept values(2,'DEPT4',NULL);
insert into dept values(2,'DEPT5',NULL);
insert into dept values(2,'DEPT6',NULL);
Approach 1:
TYPE deptname IS RECORD(op_deptname dept.deptname%TYPE);
TYPE cursor_deptname IS REF CURSOR RETURN deptname;
CREATE OR REPLACE PROCEDURE get_deptname(ip_sno IN dept.sno%TYPE, op_cursor OUT cursor_deptname);
IS
vv_dept_name dept.deptname%type;
BEGIN
LOCK TABLE dept IN EXCLUSIVE MODE;
OPEN op_cursor FOR
SELECT deptname
FROM dept
WHERE sno = ip_sno
AND start_time IS NULL
AND ROWNUM <= 3;
LOOP
FETCH op_cursor INTO vv_dept_name;
EXIT WHEN op_cursor%NOTFOUND;
UPDATE dept
SET start_time = sysdate
WHERE deptname = vv_dept_name;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
IF op_cursor%ISOPEN THEN CLOSE op_cursor; END IF;
END;
Proc execution from application.
DECLARE
i deptname;
c_cursor cursor_deptname;
BEGIN
get_deptname(2,c_cursor);
LOOP
FETCH c_cursor INTO i;
DBMS_OUTPUT.PUT_LINE('name:'||i.op_deptname);
EXIT WHEN c_cursor %NOTFOUND;
END LOOP;
END;
With this approach I'm able to update the table with date but dept names are not retrieved in the cursor.
Here's how I understood the question.
Table contents:
SQL> select * from tdept;
SNO DEPTNAME START_TIME
---------- ---------------------------------------- ----------
1 DEPT1
1 DEPT2
1 DEPT3
2 DEPT4
2 DEPT5
2 DEPT6
6 rows selected.
The procedure, which
updates the table
returns refcursor
SQL> create or replace procedure get_deptname
2 (ip_sno in tdept.sno%type,
3 op_cursor out sys_refcursor
4 )
5 is
6 begin
7 update tdept t set
8 t.start_time = sysdate
9 where t.sno = ip_sno;
10
11 open op_cursor for
12 select deptname
13 from tdept
14 where sno = ip_sno;
15 end;
16 /
Procedure created.
Execution & the result:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> var lout refcursor
SQL>
SQL> exec get_deptname(2, :lout);
PL/SQL procedure successfully completed.
SQL> select * from tdept;
SNO DEPTNAME START_TIME
---------- ---------------------------------------- -------------------
1 DEPT1
1 DEPT2
1 DEPT3
2 DEPT4 10.04.2018 20:32:23
2 DEPT5 10.04.2018 20:32:23
2 DEPT6 10.04.2018 20:32:23
6 rows selected.
SQL> print lout
DEPTNAME
----------------------------------------
DEPT4
DEPT5
DEPT6
SQL>
I have a problem with the procedure that should remove one product at a time based on the order number and the product number from the tables below. I tried the procedure initially and it worked only when removing the first pno from odetails, and inserting into the required tables. However, my problem is when an order has more than one product number and wanted to remove One By One. I usually get an error with the last item when I try to remove it and also the order won't be deleted because there is no delete on this table. In summary How do to remove the last ono from orders once all the removing processes finish? Any help would be much appreciated!
create or replace PROCEDURE ONE_BY_ONE( ORD_NUM IN NUMBER(5), PRO_NUM IN NUMBER(5))
AS
CURSOR CUR1
IS
SELECT * FROM ORDERS
WHERE ONO = ORD_NUM;
CURSOR CUR2
IS
SELECT * FROM ODETAILS
WHERE PNO = PRO_NUM
AND ONO = P_ONO;
VAL1 CUR1%ROWTYPE;
VAL2 CUR2%ROWTYPE;
BEGIN
OPEN CUR1;
OPEN CUR2;
LOOP
FETCH CUR1 INTO VAL1;
FETCH CUR2 INTO VAL2;
EXIT WHEN CUR1%NOTFOUND;
EXIT WHEN CUR1%NOTFOUND;
IF VAL1.SHIPPED IS NOT NULL THEN
IF PRO_NUM = VAL2.PNO AND ORD_NUM = VAL2.ONO THEN
INSERT INTO (ID_NUMBER, ONO,CNO,DATE_SEND,CONDITION)
VALUES(Seq.NEXTVAL, VAL1.ONO,VAL1.CNO,SYSDATE,'SEND BACK');
INSERT INTO REMOVED_ODETAILS(ONO,PNO,QTY) VALUES (VAL2.ONO, VAL2.PNO, VAL2.QTY);
END IF;
DELETE FROM ODETAILS WHERE ONO = ORD_NUM AND PNO = PRO_NUM;
DBMS_OUTPUT.PUT_LINE('Item will be send back ');
-- DELETE FROM ORDERS WHERE ONO = ORD_NUM;
ELSIF VAL1.SHIPPED IS NULL THEN
DBMS_OUTPUT.PUT_LINE('DO NOTHING ');
END IF;
END LOOP;
CLOSE CUR1;
CLOSE CUR2;
END ONE_BY_ONE;
ORDERS
ONO CNO ENO RECEIVED SHIPPED
---------- ---------- ---------- --------- ---------
1112 1010 9999 18-JAN-16 15-JAN-16
ODETAILS
ONO PNO QTY
---------- ---------- ----------
1112 12345 1
1112 67891 4
Just answering your question, you can add to your delete
DELETE FROM ORDERS
WHERE ONO = ORD_NUM and
NOT EXISTS (select 1 from ODETAILS where ONO = ORD_NUM );
After that delete will be executed only if it's the last product in the order.
I have 2 tables in which ID field is common.
I am fetching all records of first table in a cursor.
Then I want to do is that on the basis of each ID from cursor, I want to get the values from second table and then return that.
How can I do that...
Please help !!!
homework?
this is basic SQL. generally you'd join the two tables.
begin
for r_row in (select b.*
from tab1 a
inner join tab2 b
on b.id = a.id)
loop
null; -- do whatever
end loop;
end;
/
if you have an existing cursor and can't change it
eg where your_cursor is just returning an ID column.
begin
open your_cursor;
loop
fetch your_cursor into v_id;
exit when your_cursor%notfound;
for r_row in (select * from tab2 b where b.id = v_id)
loop
null; -- do whatever here.
end loop;
end loop;
end;
/
edit:
as per comments:
some sample data:
SQL> create table table1 (id number primary key, name varchar2(20));
Table created.
SQL> create table table2 (id number, col1 varchar2(20), col2 varchar2(20));
Table created.
SQL> insert into table1 values (1, 'test');
1 row created.
SQL> insert into table1 values (2, 'foo');
1 row created.
SQL>
SQL> insert into table2 values (1, 'John', 'Smith');
1 row created.
SQL> insert into table2 values (1, 'Peter', 'Jones');
1 row created.
SQL> insert into table2 values (1, 'Jane', 'Doe');
1 row created.
SQL> insert into table2 values (2, 'Nina', 'Austin');
1 row created.
SQL> insert into table2 values (2, 'Naman', 'Goyal');
1 row created.
SQL> commit;
Commit complete.
create a type to hold the return structure. note the datatypes NEED to match the datatypes of the tables table1 and table2 (%type won't work, so make sure they match)
SQL> create type my_obj as object (
2 id number,
3 name varchar2(20),
4 col1 varchar2(20),
5 col2 varchar2(20)
6 );
7 /
Type created.
SQL> create type my_tab as table of my_obj;
2 /
Type created.
now create your function (you can put this in a package if, in your real code, you have it that way).
SQL> create function function1
2 return my_tab pipelined
3 is
4 begin
5 for r_row in (select t1.id, t1.name, t2.col1, t2.col2
6 from table1 t1
7 inner join table2 t2
8 on t1.id = t2.id)
9 loop
10 pipe row(my_obj(r_row.id, r_row.name, r_row.col1, r_row.col2));
11 end loop;
12 end;
13 /
Function created.
SQL>
SQL> select *
2 from table(function1);
ID NAME COL1 COL2
---------- -------------------- -------------------- --------------------
1 test John Smith
1 test Peter Jones
1 test Jane Doe
2 foo Nina Austin
2 foo Naman Goyal
you could pass inputs if required into that function eg table(function1('a', 'b')); etc..