pl/sql ,oracle add heading in cursor - sql

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>

Related

How to dbms_output record variable

set serveroutput on;
DECLARE
PI_STARTDATE DATE;
PO_STATUS NUMBER;
PO_HEADER VARCHAR2(200);
PO_LABEL VARCHAR2(200);
PO_RECORD SYS_REFCURSOR;
PO_NEXTINCSTARTDATE DATE;
BEGIN
PI_STARTDATE := to_date('2020-05-01','yyyy-MM-dd');
PCK_FAB_REPORTS.PRC_MONTHLY_WRTOFF_REPORT(
PI_STARTDATE => PI_STARTDATE,
PO_STATUS => PO_STATUS,
PO_HEADER => PO_HEADER,
PO_LABEL => PO_LABEL,
PO_RECORD => PO_RECORD,
PO_NEXTINCSTARTDATE => PO_NEXTINCSTARTDATE);
DBMS_OUTPUT.PUT_LINE('PO_STATUS = ' || PO_STATUS);
DBMS_OUTPUT.PUT_LINE('PO_HEADER = ' || PO_HEADER);
DBMS_OUTPUT.PUT_LINE('PO_LABEL = ' || PO_LABEL);
DBMS_OUTPUT.PUT_LINE('PO_NEXTINCSTARTDATE = ' || PO_NEXTINCSTARTDATE);
END;
I want to unit test the procedure and want to display the ref cursor variable as well. How to display ref cursor using dbms?
Here's an example based on Scott's EMP table:
SQL> set serveroutput on
SQL>
SQL> declare
2 l_ename emp.ename%type;
3 l_job emp.job%type;
4 l_rc sys_refcursor;
5 begin
6 open l_rc for select ename, job from emp
7 where deptno = 10;
8
9 loop
10 fetch l_rc into l_ename, l_job;
11 exit when l_rc%notfound;
12
13 dbms_output.put_line(l_ename ||' '|| l_job);
14 end loop;
15 close l_rc;
16 end;
17 /
CLARK MANAGER
KING PRESIDENT
MILLER CLERK
PL/SQL procedure successfully completed.
SQL>
So: you have to fetch refcursor into variables and then display contents of those variables.

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.

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

Every column on different line with one record SQL reports

I am trying to make a report for a particular table emp something like this.
************************************************************
EMPLOYEE NUMBER : 1010
EMPLOYEE NAME : SARAH
JOB : DESIGNER
SALARY : 10000$
*************************************************************
EMPLOYEE NUMBER : 1011
EMPLOYEE NAME : HANNAH
JOB : DECORATOR
SALARY : 20000$
*************************************************************
But I don't know how to get every column name on different line.
sql > break column on empname skip page
The above code does something like this.
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7839 KING PRESIDENT 5000
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7840 QUEEN PRESIDENT 4000
Please suggest what would be the code for it ? TIA.
If you just want the data in this format in some presentation layer (e.g. a text editor), you can simply select the columns concatenated together, separated by a newline:
DECLARE
CURSOR cur IS
SELECT empno, ename, job, sal FROM yourTable;
v_empno yourTable.empno%INT;
v_ename yourTable.ename%VARCHAR2(100);
v_job yourTable.job%VARCHAR2(100);
v_sal yourTable.sal%NUMBER(10,4);
BEGIN
OPEN cur;
LOOP
FETCH cur INTO v_empno, v_ename, v_job, v_sal
EXIT WHEN cur%NOTFOUND;
dbms_output.put_line('EMPLOYEE NUMBER : ' || v_empno || CHR(13) || 'EMPLOYEE NAME : '
|| v_ename || CHR(13) || 'JOB : ' || v_job || CHR(13) || 'SALARY : ' ||
v_sal || '$' || CHR(13));
END LOOP;
CLOSE cur;
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>