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

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.

Related

Compilation error in Stored Procedure (Oracle SQL)

create or replace function getAvg(id1 IN number, id2 IN number) return number as
sal1 number;
sal2 number;
avg number;
BEGIN
select esal into sal1 from employees where eno = id1;
select esal into sal2 from employees where eno = id2;
avg := (sal1+sal2)/2;
return avg;
END;
/
When I try to compile the above code, I get compilation errors with following message:
Warning: Function created with compilation errors.
But when I replace avg after return with (sal1+sal2)/2 it compiles successfully.
That's bad habits: never name your own objects, variables, whatever using reserved words or keywords. avg is a built-in function; rename the variable:
SQL> create or replace function getAvg(id1 IN number, id2 IN number)
2 return number
3 as
4 sal1 number;
5 sal2 number;
6 l_avg number;
7 BEGIN
8 select esal into sal1 from employees where eno = id1;
9 select esal into sal2 from employees where eno = id2;
10 l_avg := (sal1+sal2)/2;
11 return l_avg;
12 END;
13 /
Function created.
SQL> select * from employees;
ENO ESAL
---------- ----------
1 100
2 200
SQL> select getavg(1, 2) from dual;
GETAVG(1,2)
-----------
150
SQL>

How to Loop department names

Oracle SQL Developer
I want to Loop department id and department names starting from dep_id = 10 and till 50th department. Departments are increasing by 10, so, there are 5 departments from 10 to 50.
Here's my code
DECLARE
dep_name VARCHAR(15);
dep_id NUMBER;
BEGIN
SELECT department_name, department_id INTO dep_name, dep_id FROM Departments
WHERE DEPARTMENT_ID = 10;
LOOP
IF dep_id < 51 THEN
DBMS_OUTPUT.PUT_LINE('Deparment id is ' || dep_id);
dep_id := dep_id + 10;
DBMS_OUTPUT.PUT_LINE('Deparment name is ' || dep_name);
ELSE
EXIT WHEN dep_id > 51;
END IF;
END LOOP;
END;
And here is the output,
Deparment id is 10
Deparment name is Administration
Deparment id is 20
Deparment name is Administration
Deparment id is 30
Deparment name is Administration
Deparment id is 40
Deparment name is Administration
Deparment id is 50
Deparment name is Administration
But as you can see the Administration row is repeating itself.
The output should be like this
Deparment id is 10
Deparment name is Administration
Deparment id is 20
Deparment name is Marketing
Deparment id is 30
Deparment name is Purchasing
Deparment id is 40
Deparment name is Human Resources
Deparment id is 50
Deparment name is Shipping
What am I supposed to do about this?
Thanks!
How about a simple cursor FOR loop?
SQL> set serveroutput on;
SQL> begin
2 for cur_d in (select department_id, department_name
3 from departments
4 where department_id between 10 and 50
5 )
6 loop
7 dbms_output.put_line('Department ID is ' || cur_d.department_id);
8 dbms_output.put_line('Department name is ' || cur_d.department_name);
9 end loop;
10 end;
11 /
Department ID is 10
Department name is Administration
Department ID is 20
Department name is Marketing
Department ID is 30
Department name is Purchasing
Department ID is 40
Department name is Human Resources
Department ID is 50
Department name is Shipping
PL/SQL procedure successfully completed.
SQL>
If the loop is mandatory
DECLARE
dep_name VARCHAR(15);
dep_id NUMBER;
BEGIN
dep_id:=10;
LOOP
IF dep_id < 51 THEN
DBMS_OUTPUT.PUT_LINE('Deparment id is ' || dep_id);
SELECT department_name INTO dep_name FROM Departments
WHERE DEPARTMENT_ID = dep_id;
DBMS_OUTPUT.PUT_LINE('Deparment name is ' || dep_name);
dep_id := dep_id + 10;
ELSE
EXIT WHEN dep_id > 51;
END IF;
END LOOP;
END;
You could avoid the loop .. using a simply (correct) query
SELECT department_name, department_id
FROM Departments
WHERE DEPARTMENT_ID between 10 AND 50;
(your code is wrong, you are only selecting the id = 10 not the values from 10 to 50 )
It seems the major requirement is to print department id and name. That does indeed require a plsql loop; and a cursor to hold the data after the select. Further, you should NOT relay on a specific interval between department id. Instead let the query and the cursor holding result set control the entire process.
declare
cursor c_department_list is
select department_name, department_id
from departments
where department_id <= 50 ;
l_dept_name departments.department_name%type;
l_dept_id departments.department_id%type;
begin
open c_department_list;
loop
fetch c_department_list
into l_dept_name, l_dept_id;
exit when c_department_list%notfound.
dbms_output.put_line(' Deparment id is ' || l_dep_id ||
'. Deparment name is ' || l_dep_name);
end loop;
close c_department_list;
end;
References: Cursors, Loop
The above uses a technique referred to as explicit cursor. There is another preferred (perhaps easier) technique referred to as implicit cursor. I will leave that to your research.

PL/SQL how to display data from select statement inside procedure?

I am not sure how to display my select statement in my procedure. This is my code inside my procedure:
CREATE OR REPLACE PROCEDURE numberOfSupplier (X INT:=0)
AS
rName REGION.R_NAME%TYPE;
nName NATION.N_NAME%TYPE;
sNKeyC SUPPLIER.S_NATIONKEY%TYPE;
BEGIN
FOR rec IN(
SELECT R.R_NAME, N.N_NAME, COUNT(S.S_NATIONKEY)
INTO rName, nName, sNKeyC
FROM REGION R, NATION N, SUPPLIER S
WHERE R.R_REGIONKEY = N.N_REGIONKEY
AND S.S_NATIONKEY = N.N_NATIONKEY
GROUP BY R.R_NAME, N.N_NAME
HAVING COUNT(S.S_NATIONKEY) > X)
LOOP
dbms_output.put_line('R_NAME'||rName);
dbms_output.put_line('N_NAME'||nName);
dbms_output.put_line('COUNT(S_NATIONKEY)'||sNKeyC);
END LOOP;
END;
/
--executing numberOfSupplier
EXECUTE numberOfSupplier(130);
This is what I get, which has no errors but not what I want:
SQL> EXECUTE numberOfSupplier(130);
R_NAME
N_NAME
COUNT(S_NATIONKEY)
R_NAME
N_NAME
COUNT(S_NATIONKEY)
R_NAME
N_NAME
COUNT(S_NATIONKEY)
R_NAME
N_NAME
COUNT(S_NATIONKEY)
What I want to get is this:
R_NAME N_NAME COUNT(S.S_NATIONKEY)
------------------------- ------------------------- --------------------
ASIA INDONESIA 131
ASIA CHINA 145
MIDDLE EAST SAUDI ARABIA 132
EUROPE GERMANY 132
I am able to get the above result if I just execute the select statement, however I dont know how to put this select statement in my procedure and get the table above:
SELECT R.R_NAME, N.N_NAME, COUNT(S.S_NATIONKEY)
FROM REGION R, NATION N, SUPPLIER S
WHERE R.R_REGIONKEY = N.N_REGIONKEY
AND S.S_NATIONKEY = N.N_NATIONKEY
GROUP BY R.R_NAME, N.N_NAME
HAVING COUNT(S.S_NATIONKEY) > 130;
Can someone explain to me why and how to fix this. Thankyou very much.
Put headers out of the loop and then, in the loop, concatenate all values you'd want to display. Use RPAD to nicely align values.
Apart from that, you misused FOR loop; you don't select INTO within its select statement, but use cursor variable. I don't have your tables so I used Scott's, for illustration:
SQL> CREATE OR REPLACE PROCEDURE numberOfSupplier (X INT:=0)
2 AS
3 BEGIN
4 dbms_output.put_line(rpad('R_NAME', 15, ' ') ||
5 rpad('N_NAME', 15, ' ') ||
6 'COUNT(S_NATIONKEY)'
7 );
8 dbms_output.put_line(rpad('-', 14, '-') || ' ' ||
9 rpad('-', 14, '-') || ' ' ||
10 rpad('-', 14, '-'));
11 FOR rec IN(
12 SELECT d.dname R_NAME,
13 e.ename N_NAME,
14 COUNT(*) snkeyc
15 FROM emp e join dept d on e.deptno = d.deptno
16 where e.deptno = 10
17 group by d.dname, e.ename
18 )
19 loop
20 dbms_output.put_line(rpad(rec.r_name, 15, ' ') ||
21 rpad(rec.n_name, 15, ' ') ||
22 rec.sNKeyC
23 );
24 END LOOP;
25 END;
26 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec numberofsupplier;
R_NAME N_NAME COUNT(S_NATIONKEY)
-------------- -------------- --------------
ACCOUNTING KING 1
ACCOUNTING CLARK 1
ACCOUNTING MILLER 1
PL/SQL procedure successfully completed.
SQL>
Changing your code slightly and using cursors instead must do your job.
create or replace PROCEDURE numberOfSupplier (X INT:=0)
AS
CURSOR rec IS SELECT R.R_NAME, N.N_NAME, COUNT(S.S_NATIONKEY) counter
FROM REGION R, NATION N, SUPPLIER S
WHERE R.R_REGIONKEY = N.N_REGIONKEY
AND S.S_NATIONKEY = N.N_NATIONKEY
GROUP BY R.R_NAME, N.N_NAME
HAVING COUNT(S.S_NATIONKEY) > X; -- cursor to collect all the Objects
BEGIN
dbms_output.put_line('R_NAME'||CHR(9)||'N_NAME'||CHR(9)||'COUNT(S_NATIONKEY)');
dbms_output.put_line('--------------------------------------------------');
FOR rec_obj IN rec LOOP
dbms_output.put_line(rec_obj.R_NAME||CHR(9)||CHR(9)||rec_obj.N_NAME||CHR(9)||CHR(9)||rec_obj.counter);
END LOOP;
EXCEPTION -- exception handlers begin
WHEN no_data_found THEN --catches exception when No Data Found
dbms_output.put_line('No Data Found');
WHEN TOO_MANY_ROWS THEN -- More than 1 row seleced
dbms_output.put_line('More than 1 row seleced');
WHEN OTHERS THEN -- handles all other errors
ROLLBACK;
dbms_output.put_line('I AM HERE!!!'
|| sqlcode
|| ' '
|| sqlerrm);
END;
/

pl-sql errror on select HR schema

i want to do something like that.. but in a function.. select * from employees where employee_id = &employee_id in HR schema of oracle
Q: Create Function “SEARCH_EMPLOYEE” that receives an Employee ID and returns all its attributes through an output parameter with a data structure that represents all its information
R:
Create or replace FUNCTION get_complete_employee (&in_employee_id IN NUMBER)
AS person_details;
BEGIN
SELECT * --'Name-'||first_name||' '|| last_name
into person_details
FROM employees;
Dbms_output.put_line(person_details);
-- END get_complete_employee;
end;
i have a error of sintax i guess..
i don't know what is wrong
If you are learning how to create the function which returns entire row of the table then following is the basic example of doing the same:
Table data:
SQL> select * from EMP;
EMP_ID EMP_NAME E
---------- -------------------- -
10 John N
20 May Y
SQL>
Function:
SQL> CREATE OR REPLACE FUNCTION GET_COMPLETE_EMPLOYEE (
2 IN_EMPLOYEE_ID IN NUMBER
3 ) RETURN EMP%ROWTYPE AS
4 MY_ROWTYPE EMP%ROWTYPE;
5 BEGIN
6 SELECT
7 * --'Name-'||first_name||' '|| last_name
8 INTO MY_ROWTYPE
9 FROM
10 EMP
11 WHERE
12 EMP_ID = IN_EMPLOYEE_ID;
13
14 RETURN MY_ROWTYPE;
15 END;
16 /
Function created.
SQL>
Calling the function and result:
SQL> SET SERVEROUT ON
SQL>
SQL> DECLARE
2 EMPTYPE EMP%ROWTYPE;
3 BEGIN
4 EMPTYPE := GET_COMPLETE_EMPLOYEE(10);
5 DBMS_OUTPUT.PUT_LINE(EMPTYPE.EMP_NAME);
6 END;
7 /
John
You must have to handle multiple exception scenarios in real-world coding.
Cheers!!

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

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; /