PL/SQL Defining the select statement for sum in If and loop - sql

For an example, we have employer salary and department based in emp table.
In SQL , we can retrieve the total salary per department just by doing this
SELECT SUM(SAL) FROM EMP WHERE DEPTNO = 20 ; //Lets put it we finding sum for dept 20.
But where else for pl/sql , I think I am syntactically wrong . I am using cursor to store all the datas. in my if statement which is inside a loop below , I tried my logic with
if deptno = 20 THEN
totalSalary = sum(salary);
DBMS_OUTPUT.PUT_LINE('Department : 20' || total Salary);
This is my actual code .
DECLARE
msal emp.salary%TYPE;
mdept emp.departmentNo%TYPE;
new_salary number(10,2);
CURSOR E1 IS select salary , departmentNo from emp;
BEGIN
OPEN E1;
LOOP
FETCH E1 into msal , mdeptno;
IF mdeptno = 20 THEN
---- I cant seems to find the right logic here to print out the total sum for department 20---
END IF;
EXIT WHEN E1%NOTFOUND;
END LOOP;
CLOSE E1;
END;
/
Thanks in advance

There is no need to use PL/SQL for thing which you can do with SQL. But if you are really need it, use following:
DECLARE
msal emp.salary%TYPE;
mdept emp.departmentNo%TYPE;
new_salary number(10,2);
CURSOR E1 (p_dept number) IS
select sum(salary) from emp where departmentNo = p_dept;
BEGIN
OPEN E1(20);
FETCH E1 into msal;
dbms_output.put_line(msal);
CLOSE E1;
END;
/

You can introduce another variable for sum aggregation and in each loop iteration add it up.
And then print out once required.
DECLARE
msal emp.salary%TYPE;
mdept emp.departmentNo%TYPE;
sum_salary number(10,2) := 0;
CURSOR E1 IS select salary , departmentNo from emp;
BEGIN
OPEN E1;
LOOP
FETCH E1 into msal , mdeptno;
sum_salary := sum_salary + msal;
IF mdeptno = 20 THEN
dbms_output.put_line(sum_salary);
EXIT WHEN E1%NOTFOUND;
END LOOP;
CLOSE E1;
PL/SQL allows you to access row by row, ( that is done by SQL behind the scenes for you)

Keep a running total with the variable, totalSalary
The algorithm is as follows:
-fetch values from cursor, e1, and assign to msal and mdeptno
-for each fetched value of msal (within the loop), keep a running total using the variable, tot_sal (or totalSalary as you originally posted)
With your approach, the assignment,totalSalary := sum(salary);, does nothing but throws errors because salary is not defined as a variable. The cursor fetches the value of salary (mine is sal) and it is assigned to msal.
If the assignment was totalSalary := sum(msal);, then it would writes over the previous fetched value and does not keep track of a running total.
The standard way to do this is initialize tot_sal to zero (or totalSalary) before the loop and then keep a running total for each fetched record:
tot_sal := tot_sal + msal;
Here is the simple sql result juxtaposed with the anonymous block:
SCOTT#dev>SELECT
2 SUM(sal)
3 FROM
4 emp
5 WHERE
6 deptno = 20;
SUM(SAL)
10875
SCOTT#dev>DECLARE
2 msal emp.sal%TYPE;
3 mdeptno emp.deptno%TYPE;
4 tot_sal emp.sal%TYPE;
5 CURSOR e1 IS
6 SELECT
7 sal,
8 deptno
9 FROM
10 emp;
11
12 BEGIN
13 tot_sal := 0;
14 OPEN e1;
15 LOOP
16 FETCH e1 INTO msal,mdeptno;
17 IF
18 mdeptno = 20
19 THEN
20 tot_sal := tot_sal + msal;
21 END IF;
22
23 EXIT WHEN e1%notfound;
24 END LOOP;
25
26 CLOSE e1;
27 dbms_output.put_line(tot_sal);
28 END;
29 /
10875
PL/SQL procedure successfully completed.

thank you for all your response ,
I am assigned to do this in pl/sql ,
i appreciate all the response from you guys. I have come out with the easiest technique in getting the result for sum, min and max and avg.
What i basically did was using the inbuild function that was already in sql and implement it in my select statement . So i have solve it by
DECLARE
totalSal emp.salary%TYPE;
maxSal emp.salary%TYPE;
minSal emp.salary%TYPE;
mdept emp.departmentNo%TYPE := 20; /* for an example getting all values for dept 20 */
CURSOR E1 IS select sum(salary) , max(salary) , min(sal) from emp where
departmentNo = mdept group by departmentNo;
BEGIN
OPEN E1;
LOOP
FETCH E1 into totalSal , maxSal , minSal;
EXIT WHEN E1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('...' || maxsal); /*...so on for display*/
END LOOP;
CLOSE E1;
END; /

Related

pl/sql ,oracle add heading in cursor

how I can add heading for table shown at top , in my code like this pic:
enter image description here
declare
E_Name employ.name%type;
E_Salary employ.salary%type;
CURSOR c_employees is
SELECT name , salary from employ order by salary desc;
BEGIN
OPEN c_employees;
LOOP
FETCH c_employees into E_Name,E_Salary ;
EXIT WHEN c_employees%notfound;
dbms_output.put_line( rpad(E_Name, 20, '.') || ' ' || rpad('$', (E_Salary/100), '$')||' '||E_Salary);
END LOOP;
CLOSE c_employees;
END;
/
Include two more DBMS_OUTPUT.PUT_LINEs which will display that header (lines #2 and 3).
For example (using my tables as I don't have yours):
SQL> begin
2 dbms_output.put_line('Emloyee name Salary');
3 dbms_output.put_line('------------ ------');
4
5 for cur_r in (select ename, sal from emp where deptno = 10) loop
6 dbms_output.put_line(rpad(cur_r.ename, 12, ' ') ||' '||
7 to_char(cur_r.sal, '99990'));
8 end loop;
9 end;
10 /
Emloyee name Salary
------------ ------
CLARK 2450
KING 5000
MILLER 1300
PL/SQL procedure successfully completed.
SQL>

WITH clause with FUNCTION AND PROCEDURE, where is the mistake?

I have table 'Studies' (with columns: student_id, name, surname, course_name, mark) and I have a task to write a PL/SQL program, where will be WITH word + FUNCTION word + PROCEDURE word.I decided to make such a program: the function will calculate the average mark for some course (input parameter) and the procedure will display information about students whose mark in this course is higher than the average. I managed to create a function that returns the average score,
create or replace FUNCTION average_mark(co_name IN VARCHAR2) RETURN REAL IS
iter NUMBER := 0;
aver NUMBER := 0;
CURSOR c1
IS
SELECT mark
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c1
LOOP
iter := iter + 1;
aver := aver + student.mark;
END LOOP;
RETURN ROUND((aver/iter),2);
END above_average_mark;
and procedure which displays information about a student whose mark in the course is more than a certain one, how now to connect the procedure and the function and the WITH word?
CREATE OR REPLACE PROCEDURE above(co_name IN VARCHAR2) IS
CURSOR c2
IS
SELECT *
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c2
LOOP
IF (student.mark > 4) THEN
DBMS_OUTPUT.PUT_LINE('name: ' || student.student_name || ', mark: ' || student.mark);
END IF;
END LOOP;
END;
i need something like this:
WITH
PROCEDURE
FUNCTION
I don't have your tables to illustrate it, so I'll use Scott's sample schema to calculate average salaries for departments.
As you said that you need something like WITH PROCEDURE FUNCTION, the only thing you have to do is to follow syntax.
Therefore, here you are: with factoring clause in this example contains a procedure which displays department name and average salary; function calls the procedure (and passes department number and average salary it calculated). Also, as any other function it actually returns a value.
SQL> set serveroutput on;
SQL> with
2 procedure p_deptno (par_deptno in dept.deptno%type,
3 par_avgsal in number)
4 is
5 l_dname dept.dname%type;
6 begin
7 select dname into l_dname
8 from dept
9 where deptno = par_deptno;
10 dbms_output.put_line('Average salary for department ' || l_dname ||
11 ' = ' || par_avgsal);
12 end p_deptno;
13
14 function f_avgsal (par_deptno in dept.deptno%type)
15 return number
16 is
17 l_avgsal number;
18 begin
19 select round(avg(e.sal)) into l_avgsal
20 from emp e
21 where e.deptno = par_deptno;
22
23 p_deptno (par_deptno, l_avgsal);
24
25 return l_avgsal;
26 end f_avgsal;
27 select f_avgsal (a.deptno) avg_sal
28 from dept a;
29 /
Result:
AVG_SAL
----------
2917
2175
1567
Average salary for department ACCOUNTING = 2917
Average salary for department RESEARCH = 2175
Average salary for department SALES = 1567
Average salary for department OPERATIONS =
SQL>
Now, adjust it to your tables & data.

Why do I get the error message that my cursor is invalid?

SET SERVEROUTPUT ON
SET VERIFY OFF
DECLARE
v_deptno empcopy.deptno%TYPE;
v_empno empcopy.empno%TYPE;
v_sal empcopy.sal%TYPE;
v_bonus NUMBER(7,2);
CURSOR emp_cursor IS
SELECT deptno, empno, sal
FROM empcopy
WHERE v_deptno < 25;
BEGIN
FETCH emp_cursor INTO v_deptno, v_empno, v_sal;
FOR r_emp in emp_cursor LOOP
IF v_sal < 3000 THEN
v_bonus := v_sal * 1.1;
ELSE
v_bonus := v_sal * 1.12;
v_deptno := v_deptno;
v_empno := v_empno;
v_sal := v_sal;
END IF;
INSERT INTO emp3
VALUES(v_empno, v_deptno, v_sal);
FETCH emp_cursor INTO v_deptno, v_empno, v_sal;
END LOOP;
CLOSE emp_cursor;
DBMS_OUTPUT.PUT_LINE(v_deptno || v_empno || v_bonus);
END;
/
SET SERVEROUTPUT OFF
SET VERIFY ON
SQL> # emp3
DECLARE
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 11
I was wondering why it says I have an invalid cursor. I have tried to change the name of the value that has to be < 25 but it didn't work.
Code you wrote is full of errors (you already know that), I don't know whether I'll catch them all, but I'll try:
cursor selects data whose v_deptno < 25. But, v_deptno looks like a locally declared variable (the first one you declared), so - unless there's a column whose name is v_deptno, it'll fail.
besides, hardcoding deptno value to "25" doesn't scale at all. I'd suggest you to switch to a stored procedure which accepts department number as a parameter
the first executable line is FETCH, but it should have been OPEN (if you choose to do everything
manually:
declare a cursor
declare cursor variable (or separate variables, as you did)
open the cursor
loop
fetch from the cursor
exit when cursor%notfound
do something
end loop
close cursor
or, better, using cursor FOR loop where Oracle does all the dirty job for you:
FOR cursor in (SELECT statement) LOOP
do something
end loop
when you're checking salary (in IF), ELSE is ... more than strange. What's the purpose of v_deptno := v_deptno; (as well as other 3 variables you set to what they were)?
INSERT statement: it lacks column list you're inserting into. This is not an error (might fail if you did it wrong), but - it is confusing, especially when there are many columns involved).
but, you're inserting V_SAL. OK. Why did you compute V_BONUS, then? You never, ever used it (except in DBMS_OUTPUT, but that just displays the value, never does anything with it as far as data in the database is concerned)
OK, now my attempt, if I may.
First, code you wrote, fixed (I'm inserting V_BONUS value):
SQL> declare
2 v_bonus number;
3 begin
4 for cur_r in (select deptno, empno, sal
5 from empcopy
6 where deptno < 25
7 )
8 loop
9 if cur_r.sal < 3000 then
10 v_bonus := cur_r.sal * 1.1;
11 else
12 v_bonus := cur_r.sal * 1.12;
13 end if;
14
15 insert into emp3 (empno, deptno, sal)
16 values (cur_r.empno, cur_r.deptno, v_bonus);
17 end loop;
18 end;
19 /
PL/SQL procedure successfully completed.
SQL> select * From emp3;
EMPNO DEPTNO SAL
---------- ---------- ----------
7369 20 1100
7566 20 3272,5
7782 10 2695
7788 20 3360
7839 10 5600
7876 20 1210
7902 20 3360
7934 10 1430
8 rows selected.
SQL>
However, that can be done with a single INSERT statement at the SQL layer, you don't need PL/SQL at all. So, unless you're just practicing your PL/SQL skills, I'd suggest you to use this option:
SQL> rollback;
Rollback complete.
SQL> insert into emp3 (empno, deptno, sal)
2 select empno,
3 deptno,
4 case when sal < 3000 then sal * 1.1
5 else sal * 1.12
6 end
7 from empcopy
8 where deptno < 25;
8 rows created.
SQL> select * From emp3;
EMPNO DEPTNO SAL
---------- ---------- ----------
7369 20 1100
7566 20 3272,5
7782 10 2695
7788 20 3360
7839 10 5600
7876 20 1210
7902 20 3360
7934 10 1430
8 rows selected.
SQL>
Finally, a stored procedure option I suggested at the beginning; instead of < in the where clause, use =.
SQL> create or replace procedure p_bonus (par_deptno in empcopy.deptno%type)
2 is
3 v_bonus number;
4 begin
5 for cur_r in (select deptno, empno, sal
6 from empcopy
7 where deptno = par_deptno --> "=" instead of "<"
8 )
9 loop
10 if cur_r.sal < 3000 then
11 v_bonus := cur_r.sal * 1.1;
12 else
13 v_bonus := cur_r.sal * 1.12;
14 end if;
15
16 insert into emp3 (empno, deptno, sal)
17 values (cur_r.empno, cur_r.deptno, v_bonus);
18 end loop;
19 end;
20 /
Procedure created.
SQL> begin
2 p_bonus (par_deptno => 25);
3 end;
4 /
PL/SQL procedure successfully completed.
The table EMP3 apparently has fewer columns than the INSERT command provides data for (4).
Check the definition of that EMP3 table and make sure the table and the insert command can be matched.
After the comments/discussion, this is the code is a more readable form (I added indentation):
SET SERVEROUTPUT ON
SET VERIFY OFF
DECLARE
v_deptno empcopy.deptno%TYPE;
v_empno empcopy.empno%TYPE;
v_sal empcopy.sal%TYPE;
v_bonus NUMBER(7,2);
CURSOR emp_cursor IS
SELECT deptno, empno, sal
FROM empcopy
WHERE v_deptno < 25;
BEGIN
FETCH emp_cursor INTO v_deptno, v_empno, v_sal;
FOR r_emp in emp_cursor LOOP
IF v_sal < 3000 THEN
v_bonus := v_sal * 1.1;
ELSE
v_bonus := v_sal * 1.12;
v_deptno := v_deptno;
v_empno := v_empno;
v_sal := v_sal;
END IF;
INSERT INTO emp3
VALUES(v_empno, v_deptno, v_sal);
FETCH emp_cursor INTO v_deptno, v_empno, v_sal;
END LOOP;
CLOSE emp_cursor;
DBMS_OUTPUT.PUT_LINE(v_deptno || v_empno || v_bonus);
END;
/
SET SERVEROUTPUT OFF
SET VERIFY ON
What this code appears to be doing is:
go over every "employee" record in table EMPCOPY
for each of those records, check if the salary is lower than 3000 and calculate the bonus by multiplying salary by 1.1 if it is
if the salary is not lower than 3000, v_bonus is calculated by 1.12
after this, the new values should be INSERTed into table EMP3
finally, the values of the very last inserted record are printed out.
First off: the poor folks that make less than 3000 also only get 110% bonus while the already better off employees get 2% more on top?
I'm aware that this is common practice, but I'd advocate aiming for a better, fairer world - at least in simple coding examples like this.
Now to the code.
What this code should do, does not require the use of cursors at all. In fact, it is not a good practice in this case.
Instead, just use plain SQL to achieve the desired effect:
INSERT INTO EMP3
(empno, deptno, sal, bonus)
(SELECT
empno, deptno, sal
, case
when sal < 3000 then sal * 1.1
else sal * 1.12
end as bonus
FROM
empcopy
WHERE
deptno < 25);
The caveat here is that the target table EMPCOPY needs to have a column for BONUS. That this column is missing was likely the cause for the original issue.
Also worth noting that this approach does not print out anything.
If that is a requirement for the solution, then this should be done after the INSERT (a simple FOR LOOP over the target table should do).
I hope that helps.

Getting a PL/SQL Cursor for loop to work with input variable?

I have to produce PL/SQL block that takes the input in of an integer where n (1 ≤ n ≤ 100) and display the most popular names for male and female. If the number entered is out of that range display an invalid input. I have created a table called popular names that has 100 of the most popular names for male and female. I am struggling with my FOR loop and getting it to work with the user input. My code is below:
ACCEPT p_1 prompt 'please enter an integer between 1 and 100'
DECLARE
CURSOR name_cur IS
SELECT rank, male_given_name, female_given_name
FROM popular_names
ORDER BY rank;
v_nrank popular_names.rank%TYPE := &p_1;
v_nmale popular_names.male_given_name%TYPE;
v_nfemale popular_names.female_given_name%TYPE;
BEGIN
OPEN name_cur;
FETCH name_cur INTO v_nrank, v_nmale, v_nfemale;
IF name_cur%FOUND THEN
DBMS_OUTPUT.PUT_LINE('Rank Male Given Name Female Given Name');
DBMS_OUTPUT.PUT_LINE('----------------------------------------------');
FOR i IN 1..v_nrank LOOP
DBMS_OUTPUT.PUT_LINE((RPAD(v_nrank, 18) || ' ' ||
(RPAD(v_nmale, 18) || ' ' || v_nfemale)));
FETCH name_cur INTO v_nrank, v_nmale, v_nfemale;
EXIT WHEN name_cur%NOTFOUND;
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('Invalid number!');
END IF;
CLOSE name_cur;
END;
No need for cursor. Or use similar query in your cursor. You can use Rownum pseudocolumn in place of Row_Number() function:
SELECT * FROM
(
SELECT deptno
, empno
, ename
, Row_Number() OVER (ORDER BY empno) AS row_seq -- can order/partition by any field
-- , Rownum AS row_seq
FROM scott.emp
)
WHERE row_seq BETWEEN 1 AND 10 -- any integer as in your example --
/
DECLARE
CURSOR e_cur
IS
SELECT * FROM
(
SELECT deptno
, empno
, ename
, Row_Number() OVER (ORDER BY empno) AS row_seq --'AS' is for clarity-not required
--, Rownum AS row_seq
FROM scott.emp
)
WHERE row_seq BETWEEN 1 AND 10 -- any integer, can use variable like &num --
ORDER BY row_seq;
v_deptno scott.dept.deptno%TYPE;
v_emp_no scott.emp.empno%TYPE;
v_name scott.emp.ename%TYPE;
v_rank NUMBER;
BEGIN
OPEN e_cur;
LOOP
FETCH e_cur INTO v_deptno, v_emp_no, v_name, v_rank;
EXIT WHEN e_cur%NOTFOUND;
dbms_output.put_line(v_rank||chr(9)||v_deptno||chr(9)||v_emp_no||chr(9)||v_name);
END LOOP;
CLOSE e_cur;
END;
/

Update employees table

Guys I have the following problem:
Increase salary 15% for employees whose salary is less than 50% of their manager's salary.
Write PL/SQL procedure using cursor, loop and update.
Procedure header
Create or replace procedure inc_salary is:
. Exception if their salary after increase is more than 50% of their manager's salary.
Actually, we can do it directly like this:
update emp e
set e.salary+=e.salary*0.15
where e.salary<(select e.mgr from emp e, group by e.mgr)
Here is a picture of this table:
But I don't understand how to use the procedure. If I declare it like this, create or replace procedure inc_salary, then what should be its parameters? We can use of course loop, like
declare
for r in (select * from emp e) loop
update emp e
set r.salary+=r.salary*0.15;
where r.salary<r.mgr
exception
if r.salary >r.mgr*1.15 then
dbms.output_putline(' it can't increase');
end loop;
end;
But how to combine it together?
Why would you need a PL/SQL procedure? A simple query would do this job!
UPDATE emp
SET salary = salary * 1.15
WHERE empno IN (
SELECT e.empno
FROM emp e
JOIN emp m ON e.mgr = m.empno
WHERE e.salary < m.salary * 0.5
)
That's it!
But, if at all you need to use a procedure, you have to decide for yourself what exactly you want to do with it.
Every procedure has a set of formal parameters, which can even be an empty set. It is you who decides what to pass to a procedure. Consult your manager or architect for these situations.
declare
prec number;
procedure inc_salary(prcin number)
is
cursor cl is * from employees;
msal number(8,2);
mid number(6);
begin
for r in cl loop
mid := nvl(r.manager_id, r.employee_id);
select salary into msal from employees where employee_id = mid;
if r.salary < (msal * 0.5) then
update employees set
salary = salary * prc
where employee_id = r.employee_id;
end if;
end loop;
end inc_salary;
begin
prec := 1.5;
inc_salary(prec);
end ;