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

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

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>

PL/SQL - The function deosn't return multiple rows

I wanted to select 10 highly paid employees from the "employees" table, but the function returnes only 1 row. How to get multiple rows in this case? My subquery for selecting employees works well, but when I call function it returns 1 row.
This is my code:
CREATE OR REPLACE FUNCTION f_sal
RETURN Varchar2
IS cursor c_emp is (select first_name, last_name from (select first_name, last_name, row_number()
over(order by salary desc) as ranking from employees) where ranking <= 10);
v_first employees.first_name%type;
v_last employees.last_name%type;
begin
open c_emp;
fetch c_emp into v_first, v_last;
close c_emp;
return v_first || ' ' || v_last;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
dbms_output.put_line('Error');
when others then dbms_output.put_line('Other Error');
END;
select f_sal from dual;
Option 1: Use a collection
CREATE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST
IS
v_names SYS.ODCIVARCHAR2LIST;
BEGIN
SELECT first_name || ' ' || last_name
BULK COLLECT INTO v_names
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 2: Use a pipelined function and iterate over a cursor into a collection
CREATE OR REPLACE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST PIPELINED
IS
BEGIN
FOR n IN (
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY
)
LOOP
PIPE ROW (n.name);
END LOOP;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 3: Return a cursor
CREATE FUNCTION f_sal
RETURN SYS_REFCURSOR
IS
v_names SYS_REFCURSOR;
BEGIN
OPEN v_names FOR
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
DECLARE
v_names SYS_REFCURSOR := f_sal();
v_name VARCHAR2(100);
BEGIN
LOOP
FETCH v_names INTO v_name;
EXIT WHEN v_names%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_name );
END LOOP;
END;
/
db<>fiddle here

using dynamic sql to create column for select statement

I'm writing a stored procedure for paginated results and this result can be ordered by certain values. I did have a switch case in a select statement but because it was trying to do an orderby on rownum it was very slow.
Now I am trying to use dyanmic sql to build the query outside the select but I don't know if what I am doing is possible.
Here is my SQL in Oracle SQL Developer:
create or replace PROCEDURE Sp_tsa_trainees_pagination (
schemeid IN INT,
searchval IN VARCHAR2,
pagesize IN INT DEFAULT 20,
currentpage IN INT DEFAULT 1,
--orderby IN VARCHAR2,
cursor_ OUT SYS_REFCURSOR)
AS
-- LOCAL VARIABLES
totalcount INT;
numberofpages INT;
startposition NUMBER;
endposition NUMBER;
orderby VARCHAR2(100) := 'surname asc' ;
dynamic_query VARCHAR(255) := 'row_number() over (order by t.SURNAME DESC, t.FORENAMES DESC) AS rnum';
BEGIN
-- Get total number of trainees in scheme
select COUNT(t.ORG_REGISTRATION_ID)
into totalcount FROM v_trainee t
where t.ORG_REGISTRATION_ID = schemeid
AND t.status = 'A' and LOWER(t.trainee_name) like '%' || LOWER(searchval) || '%';
-- calculate number of pages in the pagination by dividing total number of records by how many to display for each page
numberofpages := totalcount / pagesize;
-- get start position by multiplying number of records to display for each page by current page
startposition := pagesize *( currentpage-1);
-- add calculated start position by number of records to display to get end position
endposition := startposition + pagesize;
CASE orderby
WHEN 'surname desc' THEN dynamic_query := 'row_number() over (order by t.SURNAME DESC, t.FORENAMES DESC) AS rnum';
WHEN 'surname asc' THEN dynamic_query := 'row_number() over (order by t.SURNAME ASC, t.FORENAMES ASC) AS rnum';
END CASE;
OPEN cursor_ FOR
Select * from
(
SELECT
-- order by based on selection
dynamic_query rnum,
t.ORG_REGISTRATION_ID SearchId,
t.FORENAMES Forenames,
t.FORENAME Forename,
t.SURNAME Surname,
t.person_id PersonId,
t.trainee_name TraineeName,
t.STATUS Status,
t.IPD_ANNUAL_REVIEW_DATE AnnualReviewDate,
t.ANNUAL_REVIEW_STATUS AnnualReviewStatus,
t.payment_received PaymentRecieved,
t.TRAINEE_ID TraineeId,
t.IPD_SIGNUP_DATE IpdSignupDate,
t.START_DATE StartDate,
t.END_DATE EndDate,
t.LENGTH_ON_SCHEME LengthOnScheme,
t.EMPLOYEE_NUMBER EmploymentNumber,
t.SELECTED_LEVEL SelectedLevel,
t.SELECTED_LEVEL_DESCRIPTION SelectedLevelDescription,
t.ELIGIBLE_LEVEL EligibleLevel,
t.ELIGIBLE_LEVEL_DESCRIPTION EligibleLevelDescription,
sce.FORENAMES SceForenames,
sce.FORENAME SceForename,
sce.SURNAME SceSurname,
sce.mentor_name SceName,
sce.EMPLOYEE_NUMBER SceEmployeeNumber,
de.FORENAMES DeForenames,
de.FORENAME DeForename,
de.SURNAME DeSurname,
de.mentor_name DeName,
de.EMPLOYEE_NUMBER DeEmployeeNumber,
t.COMPLETED_ATTRIBUTE_LEVELS CompletedAttributeLevels,
t.ATTRIBUTE_LEVEL_COUNT AttributeLevelCount,
-- get percentage
CASE t.ATTRIBUTE_LEVEL_COUNT
WHEN 0 THEN 0
ELSE
COMPLETED_ATTRIBUTE_LEVELS / t.ATTRIBUTE_LEVEL_COUNT * 100
END percentage,
DECODE(F_ISTRAINEEGROUPMEMBER(t.ORG_REGISTRATION_ID, 'S', t.person_id),'Y','N','Y') WithoutTsaGroup,
orr.status SchemeStatus,
(select count(*) from TRAINING_GROUP_TRAINEE tgt where tgt.trainee_id = t.TRAINEE_ID) NUMBER_OF_GROUPS,
TotalCount
FROM v_trainee t
INNER JOIN org_registration orr ON t.ORG_REGISTRATION_ID = orr.id
LEFT OUTER JOIN v_mentor sce ON t.sce_id = sce.MENTOR_ID
LEFT OUTER JOIN v_mentor de ON t.de_id = de.MENTOR_ID
where t.ORG_REGISTRATION_ID = schemeid AND t.status = 'A'
and LOWER(t.trainee_name) like '%' || LOWER(searchval) || '%'
)
where rnum >= startposition and rnum <= endposition;
END;
I want to use this variable with the assigned sql:
dynamic_query rnum,
But when I execute the stored procedure I get this error:
ORA-01722: invalid number ORA-06512: at
"db.SP_TSA_TRAINEES_PAGINATION", line 46 ORA-06512: at line 13
So basically my question is can I assign a SQL to VARCHAR2 and then use it in a select statement dynamically.
You may need dynamic SQL for this. For example:
create or replace procedure testDyn(n in number, C OUT SYS_REFCURSOR) is
vDynamicPart varchar2(1000);
vSQl varchar2(1000);
begin
--
if (n = 1) then
vDynamicPart := 'count(1)';
else
vDynamicPart := 'count(null)';
end if;
--
vSQl := 'select ' || vDynamicPart || ' from dual';
open C for vSQl;
end;
If you call it
declare
n1 number;
n2 number;
C1 SYS_REFCURSOR;
C2 SYS_REFCURSOR;
begin
testDyn(1, C1);
testDyn(2, C2);
fetch C1 into n1;
fetch C2 into n2;
dbms_output.put_line('n1: ' || n1);
dbms_output.put_line('n2: ' || n2);
end;
you get:
n1: 1
n2: 0

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

How to set an object as an out parameter?

I have written a procedure in PL/SQL and want to return a EMP type object. Is it possible to do that? If yes, how can I do it?
Here is the code:
CREATE OR REPLACE
PROCEDURE get_emp_rs (p_deptno IN emp.deptno%TYPE,
p_recordset OUT emp_det) AS
emp_details emp_det;
BEGIN
OPEN p_recordset FOR
SELECT ename,
empno
FROM emp
WHERE deptno = p_deptno
ORDER BY ename;
fetch p_recordset into emp_details;
--exit when p_recordset%notfound;
--end loop;
--for indx in p_recordset
--loop
emp_details.ename:= 'test';
--end loop;
END get_emp_rs;
/
SET SERVEROUTPUT ON SIZE 1000000
DECLARE
l_cursor emp_det;
--l_cur emp_det;
--l_ename emp.ename%TYPE;
--l_empno emp.empno%TYPE;
l_deptno emp.deptno%TYPE;
BEGIN
l_cur:=get_emp_rs ('30',
l_cursor);
dbms_output.put_line('low');
/*LOOP
FETCH l_cursor
INTO l_ename, l_empno, l_deptno;
EXIT WHEN l_cursor%NOTFOUND;*/
DBMS_OUTPUT.PUT_LINE(l_cursor.ename || ' | ' || l_cursor.empno);
end;
/
I want to get the ename and empno after finally update in the procedure.
How can I do it? If there is any better way of doing this please suggest me.
And also please suggest me how I can do it in this way. I cannot use any functions here that's the only obligation. Also please let me know if there is a way of doing this using.
In your example (Table EMP) a single EMP Record shouldn't be selected by the department number. Use the EMPNO instead.
The following is a quick solution and hopefully self-explainable:
SET SERVEROUTPUT ON;
SET FEEDBACK OFF;
CLEAR;
--Define the dataset object
CREATE TYPE EMP_DET AS OBJECT (
EMPNO NUMBER(4),
ENAME VARCHAR2(10)
);
/
--Define a collection of dataset objects
CREATE TYPE EMP_DET_LIST IS TABLE OF EMP_DET;
/
--Get a SINGLE record into the OUT-Variable identified by EMP PK EMPNO
CREATE OR REPLACE PROCEDURE GET_EMP_RS(P_EMPNO IN EMP.EMPNO%TYPE,
P_RECORDSET IN OUT EMP_DET) AS
BEGIN
--Create the return object inside SQL
SELECT EMP_DET(EMPNO, ENAME)
INTO P_RECORDSET
FROM EMP
WHERE EMPNO = P_EMPNO;
P_RECORDSET.ENAME := 'test';
EXCEPTION
WHEN NO_DATA_FOUND THEN
--Return NULL if employee not found
P_RECORDSET := NULL;
END GET_EMP_RS;
/
--Get a LIST OF employees by department
CREATE OR REPLACE PROCEDURE GET_EMP_LIST(P_DEPTNO IN EMP.DEPTNO%TYPE,
P_RECORDLIST OUT EMP_DET_LIST) AS
TYPE C_CURSOR IS REF CURSOR; -- <-- For the explicit cursor solution only
C_EMP_RS C_CURSOR; -- <-- For the explicit cursor solution only
V_RS EMP_DET; -- <-- For the explicit cursor solution only
BEGIN
--Initialize out object
P_RECORDLIST := EMP_DET_LIST();
--Create the return object inside SQL
--via bulk collect
/*
SELECT EMP_DET(EMPNO,ENAME)
BULK COLLECT INTO INTO P_RECORDLIST
FROM EMP
WHERE DEPTNO = P_DEPTNO;
*/
--with manipulation of records
--use a FOR-LOOP with implizit cursor
/*
FOR L_RS IN (SELECT EMP_DET(EMPNO, ENAME) EMP_RS
FROM EMP
WHERE DEPTNO = P_DEPTNO) LOOP
L_RS.EMP_RS.ENAME := 'TEST';
P_RECORDLIST.EXTEND;
P_RECORDLIST(P_RECORDLIST.LAST) := L_RS.EMP_RS;
NULL;
END LOOP;
*/
--or define an explicit cursor and LOOP-FETCH
OPEN C_EMP_RS FOR
SELECT EMP_DET(EMPNO, ENAME) EMP_RS
FROM EMP
WHERE DEPTNO = P_DEPTNO;
LOOP
FETCH C_EMP_RS
INTO V_RS;
EXIT WHEN C_EMP_RS%NOTFOUND;
V_RS.ENAME := 'TEST';
P_RECORDLIST.EXTEND;
P_RECORDLIST(P_RECORDLIST.LAST) := V_RS;
END LOOP;
CLOSE C_EMP_RS;
END GET_EMP_LIST;
/
--**************************
-- Test
--**************************
DECLARE
L_CURSOR EMP_DET;
L_LIST EMP_DET_LIST;
BEGIN
DBMS_OUTPUT.PUT_LINE('---- Single EMP ----');
GET_EMP_RS(7369, L_CURSOR);
IF (L_CURSOR IS NOT NULL) THEN
DBMS_OUTPUT.PUT_LINE(L_CURSOR.ENAME || ' | ' || L_CURSOR.EMPNO);
END IF;
DBMS_OUTPUT.PUT_LINE('---- EMP List ----');
GET_EMP_LIST(30, L_LIST);
IF (L_LIST.count > 0 ) THEN
FOR L_I IN L_LIST.FIRST .. L_LIST.LAST LOOP
DBMS_OUTPUT.PUT_LINE(L_LIST(L_I).ENAME || ' | ' || L_LIST(L_I).EMPNO);
END LOOP;
END IF;
END;
/
DROP PROCEDURE GET_EMP_RS;
DROP PROCEDURE GET_EMP_LIST;
DROP TYPE EMP_DET_LIST;
DROP TYPE EMP_DET;
Output:
---- Single EMP ----
test | 7369
---- EMP List ----
TEST | 7499
TEST | 7521
TEST | 7654
TEST | 7698
TEST | 7844
TEST | 7900
SQL>