Examine the following package - plsql

Package:
create or replace package manage_emps
is
tax_rate constant number(5,2) := .28;
v_id number;
procedure insert_emp(p_deptno number,p_sal number);
procedure delete_emp;
procedure update_emp;
function calc_tax(p_sal number) return number;
end;
Package body:
create or replace package body manage_emps
is
procedure update_sal(p_raise_amt number)
is
begin
update emp
set sal = (sal * p_raise_amt) + sal;
where empno = v_id;
end;
procedure insert_emp(p_deptno number,p_sal number)
is
begin
insert into emp(empno,deptno,sal)
values(v_id,p_deptno,p_sal);
end;
procedure delete_emp
is
begin
delete from emp
where empno = v_id;
end;
procedure update_emp
is
begin
v_sal number(10,2);
v_raise number(10,2);
select sal into v_sal
from emp
where empno = v_id;
if v_sal < 500 then
v_raise := .05;
elsif v_sal < 1000 then
v_raise := .07;
else
v_raise := .04;
end if;
update_sal(v_raise);
end;
function calc_tax(p_sal number) return number
is
begin
return p_sal * tax_rate;
end;
end;
The question is "How many public procedures are in MANAGE_EMPS package ?"
I answered "three". How can it be wrong ? Clearly 3 functions are mentioned in package specification. That procedure whose declaration/body is in package's body, then its private. Here in body, procedure UPDATE_SAL is private. But the correct answer is: "none". NO public procedures ? How ?

The correct answer is three. There are three public procedures in the package.
Note:
The package design could be improved. v_id should be a parameter and not a global variable.

Related

I get this Error : PLS-00103: Encountered the symbol “END” when expecting one of the following

If someone needs more information about what procedure or function need to do I would gladly explain them.
This is my package spec where I declared function and procedure :
create or replace PACKAGE SEMIR AS
/* TODO enter package declarations (types, exceptions, methods etc) here */
PROCEDURE set_sal;
FUNCTION emp_info(empno NUMBER)
return VARCHAR2;
END SEMIR;
and my package body :
create or replace PACKAGE BODY SEMIR AS
PROCEDURE set_sal AS
v_manager_sal number;
v_sal number;
v_grade number;
v_empno number;
BEGIN
SELECT sal INTO v_sal FROM EMP1;
SELECT grade INTO v_grade FROM EMP1;
UPDATE EMP1
SET oldsal = v_sal;
IF (v_sal<=3000) THEN
SELECT MAX(sal) INTO v_sal FROM EMP1
WHERE grade = grade;
SELECT sal into v_manager_sal FROM EMP1 WHERE job ='MANAGER' AND
empno = mgr;
IF(v_sal-100>v_manager_sal)
THEN
UPDATE EMP1
SET sal = sal-(sal-100-v_sal);
END IF;
END IF;
END set_sal;
FUNCTION emp_info(empno NUMBER)
return VARCHAR2 AS
v_mgr number;
BEGIN
SELECT mgr INTO v_mgr
FROM EMP1;
IF (v_mgr IS NOT NULL)
THEN
SELECT mgr INTO empno
FROM EMP1
WHERE empno=mgr;
IF (mgr IS NOT NULL)
THEN
SELECT mgr INTO empno
FROM EMP1
WHERE empno = mgr;
RETURN 'EMPLOYEE:'|| ename || ',' || job;
ELSE
RETURN job;
END IF;
END IF;
END emp_info;
END SEMIR;
I get error in package body but I dont know what is wrong. Person who gave me this to do also told me that this could be done using cursors too but i hope i could get this working. Regards
I can't reproduce that error but I do see this error:
Error: PLS-00403: expression 'EMPNO' cannot be used as an INTO-target of a SELECT/FETCH statement
Line: 41
Text: SELECT mgr INTO empno
Which is caused by trying to assign a value to a IN parameter. IN parameters are passed by reference and cannot be modified.
...
FUNCTION emp_info(empno NUMBER) --Defaults to an IN parameter.
return VARCHAR2 AS
v_mgr number;
BEGIN
SELECT mgr INTO v_mgr
FROM EMP1;
IF (v_mgr IS NOT NULL)
THEN
SELECT mgr INTO empno --Trying to modify the IN parameter.
...
In parameters are meant to be used as supplied value only and your requirement will fulfil IN OUT mode parameter.But it is recommended no to use out mode in function,so better to use a different variable other than in parameter variable.

how to Pass table name to PL SQL cursor dynamically?

I have written one SQL Procedure where I have written one cursor and every time i have to pass table name to cursor query dynamically .
create or replace
PROCEDURE Add_DEN as
v_TableName VARCHAR2(4000) := 'BO_USER_DATA';
cursor c_DEN is select * from BO_USER_DATA; // Want to pass dynamically ,now hardcoded
r_DEN c_DEN%ROWTYPE;
fetch c_DEN into r_DEN;
v_Name := r_DEN."Name";
Can i write something like this
cursor c_DEN is "select * from " || v_TableName;
Any Help ?
here an example:
declare
TYPE curtype IS REF CURSOR;
l_cursor curtype;
l_param number;
l_key number;
l_value number;
l_sql varchar2(200);
begin
/* build your sql... */
l_sql := 'with data as (select 1 key, 100 value from dual union select 2, 200 from dual union select 3, 300 from dual union select 3, 301 from dual)' ||
' select key, value from data where key = :1';
l_param := 3;
open l_cursor for l_sql
using l_param;
loop
fetch l_cursor
into l_key, l_value;
exit when l_cursor%notfound;
dbms_output.put_line(l_key||' = '||l_value);
end loop;
close l_cursor;
end;
Result:
3 = 300
3 = 301
The basic answer is yes, you can and Given your example I would recommend you to use execute immediate to execute an arbitrary SQL string and bind the variables.
I would nevertheless reconsider if you really needed to dynamically set the table as this is not very often really needed.
Example:
DECLARE
sql_stmt VARCHAR2(200);
emp_id NUMBER(4) := 7566;
emp_rec emp%ROWTYPE;
BEGIN
sql_stmt := 'SELECT * FROM emp WHERE empno = :id';
EXECUTE IMMEDIATE sql_stmt INTO emp_rec USING emp_id;
END;

F_checksal is not a procedure or is undefined when compiling plsql

When I am trying to compile this plsql I keep getting this error message:
'F_checksal is not a procedure or is undefined'
This is the code:
declare
e_high_increase EXCEPTION;
old_sal NUMBER;
function f_checkSal(i_sal number,i_old_sal IN OUT NUMBER) return VARCHAR2 is
begin
if((i_sal/old_sal)*100>300) then
raise e_high_increase;
else
return 'yes';
end if;
exception when e_high_increase then
insert into t_logerror(error_tx) values ('this is user exception');
dbms_output.put_line('this is user exception');
return 'No';
end;
begin
select sal into old_sal FROM emp where empno=153;
f_checksal(30000,old_sal);
update emp set sal=30000 where empno=153;
end;
It's just your call to your function that needs to be changed.
You can consume the result with a call to a procedure (e.g. DBMS_OUTPUT.put_line( f_checksal(30000,old_sal));), or assign it to a local variable, e.g. RESULT := f_checksal(30000,old_sal);
Your syntax is way off. You need to first CREATE the function, then reference it in your pl/sql block:
Step1 :
CREATE OR REPLACE FUNCTION f_checksal (i_sal NUMBER, i_old_sal IN OUT NUMBER)
RETURN VARCHAR2
IS
old_sal NUMBER;
e_high_increase EXCEPTION;
BEGIN
IF ((i_sal / old_sal) * 100 > 300)
THEN
RAISE e_high_increase;
ELSE
RETURN 'yes';
END IF;
EXCEPTION
WHEN e_high_increase
THEN
INSERT INTO t_logerror
(error_tx
)
VALUES ('this is user exception'
);
DBMS_OUTPUT.put_line ('this is user exception');
RETURN 'No';
END;
/
Step 2:
DECLARE
old_sal NUMBER;
RESULT VARCHAR2 (100);
BEGIN
SELECT sal
INTO old_sal
FROM emp
WHERE empno = 153;
RESULT := f_checksal (30000, old_sal);
UPDATE emp
SET sal = 30000
WHERE empno = 153;
END;
Note that your function has a return value that you were not assinging in the calling code.

TOAD displaying cursor recordset returned by stored procedure

How I could print recorset result from the returning cursor, please.
Down below executes fine but I need to see result.
This is block in TOAD, calling package sp AMD_NEEDMSG:
DECLARE
RETURN_RECORDSET CTI_MATRIX.AMD.REF_CURSOR;
BEGIN
CTI_MATRIX.AMD.AMD_NEEDMSG ( '88888888885', RETURN_RECORDSET );
END;
This package spec:
CREATE OR REPLACE PACKAGE CTI_MATRIX.AMD AS
TYPE REF_CURSOR IS REF CURSOR;
PROCEDURE AMD_NEEDMSG (v_CRN IN VARCHAR2, return_recordset OUT REF_CURSOR);
END AMD;
This is package body:
CREATE OR REPLACE PACKAGE BODY CTI_MATRIX.AMD AS
PROCEDURE AMD_NEEDMSG (v_CRN IN VARCHAR2, return_recordset OUT REF_CURSOR) IS
return_flag INTEGER;
row_cnt INTEGER;
number_of_days INTEGER;
var_DATE DATE;
CURSOR ACCNTSEARCH (P_CRN IN VARCHAR2) IS
SELECT DISTINCT COUNT(*) , AMD.MSG_DATE
FROM TBL_AMD_NEEDMSG AMD
WHERE AMD.PHONE_NUMBER = P_CRN AND ROWNUM = 1;
BEGIN
OPEN ACCNTSEARCH(v_CRN);
FETCH ACCNTSEARCH INTO row_cnt, var_DATE;
CLOSE ACCNTSEARCH;
IF (row_cnt = 0)
THEN
INSERT INTO TBL_AMD_NEEDMSG (PHONE_NUMBER, MSG_DATE) VALUES (v_CRN , SYSDATE);
return_flag := 1;
ELSE
SELECT SYSDATE-var_DATE INTO number_of_days FROM dual;
IF (number_of_days>7)
THEN
UPDATE TBL_AMD_NEEDMSG SET MSG_DATE = SYSDATE WHERE PHONE_NUMBER = v_CRN;
return_flag := 1;
ELSE
return_flag := 0;
END IF;
END IF;
COMMIT;
OPEN return_recordset FOR
SELECT return_flag AS ReturnFLag FROM DUAL;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END AMD_NEEDMSG;
END AMD;
/
Bottom line is to return to client a value of return_flag in the form of record set.
To return the return_flag, which is int=1 or 0 you need function that returns a single value, not recordset. Below are general recordset examples - hope this helps:
DECLARE
TYPE empcurtyp IS REF CURSOR;
emp_cv empcurtyp;
--
TYPE namelist IS TABLE OF scott.emp.ename%TYPE;
TYPE sallist IS TABLE OF scott.emp.sal%TYPE;
names namelist;
sals sallist;
BEGIN
OPEN emp_cv FOR
SELECT ename, sal FROM scott.emp
WHERE job = 'MANAGER' ORDER BY sal DESC;
--
FETCH emp_cv BULK COLLECT INTO names, sals;
CLOSE emp_cv;
-- loop through the names and sals collections
FOR i IN names.FIRST .. names.LAST LOOP
DBMS_OUTPUT.PUT_LINE
('Name = ' || names(i) || ', salary = ' || sals(i));
END LOOP;
END;
/
-- SYS_REFCURSOR example --
DECLARE
p_rc_type SYS_REFCURSOR;
emp_rec scott.emp%ROWTYPE;
--
PROCEDURE p_Emp_Info (p_cur_var OUT SYS_REFCURSOR)
IS
BEGIN
OPEN p_cur_var FOR
SELECT ename, job FROM scott.emp WHERE job = 'MANAGER';
LOOP
FETCH p_cur_var INTO emp_rec.ename, emp_rec.job;
EXIT WHEN p_cur_var%NOTFOUND;
dbms_output.put_line(emp_rec.ename ||' '|| emp_rec.job);
END LOOP;
CLOSE p_cur_var;
END p_Emp_Info;
--
BEGIN
p_Emp_Info(p_rc_type);
END;
/

Can I pass an explicit cursor to a function/procedure for use in FOR loop?

I have a procedure that performs some calculations on all records returned by a cursor. It looks a bit like this:
PROCEDURE do_calc(id table.id_column%TYPE)
IS
CURSOR c IS
SELECT col1, col2, col3
FROM table
WHERE ...;
BEGIN
FOR r IN c LOOP
-- do some complicated calculations using r.col1, r.col2, r.col3 etc.
END LOOP;
END;
Now I have the case where I need to perform the exact same calculation on a different set of records that come from a different table. However, these have the same "shape" as in the above in example.
Is it possible to write a procedure that looks like this:
PROCEDURE do_calc2(c some_cursor_type)
IS
BEGIN
FOR r IN c LOOP
-- do the calc, knowing we have r.col1, r.col2, r.col3, etc.
END LOOP;
END;
I know about SYS_REFCURSOR, but I was wondering if it was possible to use the much more convenient FOR ... LOOP syntax and implicit record type.
Create a package.
Declare your cursor as package variable.
Use %rowtype to set function parameter type.
create or replace package test is
cursor c is select 1 as one, 2 as two from dual;
procedure test1;
function test2(test_record c%ROWTYPE) return number;
end test;
create or replace package body test is
procedure test1 is
begin
for r in c loop
dbms_output.put_line(test2(r));
end loop;
end;
function test2(test_record c%ROWTYPE) return number is
l_summ number;
begin
l_summ := test_record.one + test_record.two;
return l_summ;
end;
end test;
I had a similar problem, where I had two cursors that needed to be processed the same way, so this is how I figured it out.
DECLARE
--Define our own rowType
TYPE employeeRowType IS RECORD (
f_name VARCHAR2(30),
l_name VARCHAR2(30));
--Define our ref cursor type
--If we didn't need our own rowType, we could have this: RETURN employees%ROWTYPE
TYPE empcurtyp IS REF CURSOR RETURN employeeRowType;
--Processes the cursors
PROCEDURE process_emp_cv (emp_cv IN empcurtyp) IS
person employeeRowType;
BEGIN
LOOP
FETCH emp_cv INTO person;
EXIT WHEN emp_cv%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Name = ' || person.f_name ||
' ' || person.l_name);
END LOOP;
END;
--Defines the cursors
PROCEDURE mainProcedure IS
emp empcurtyp;
BEGIN
OPEN emp FOR SELECT first_name, last_name FROM employees WHERE salary > 50000;
process_emp_cv(emp);
CLOSE emp;
OPEN emp FOR SELECT first_name, last_name FROM kuren WHERE first_name LIKE 'J%';
process_emp_cv(emp);
CLOSE emp;
END;
BEGIN
mainProcedure;
END;
/
You can also use this if you want to bulk collect your cursors. You just need to change your helper procedure process_emp_cv; the rest can stay the same.
Using BULK COLLECT
--Processes the cursors
PROCEDURE process_emp_cv (emp_cv IN empcurtyp) IS
TYPE t_employeeRowTable IS TABLE OF employeeRowType;
employeeTable t_employeeRowTable;
BEGIN
LOOP
FETCH emp_cv BULK COLLECT INTO employeeTable LIMIT 50;
FOR indx IN 1 .. employeeTable.Count
LOOP
DBMS_OUTPUT.PUT_LINE('Name = ' || employeeTable(indx).f_name ||
' ' || employeeTable(indx).l_name);
END LOOP;
EXIT WHEN emp_cv%NOTFOUND;
END LOOP;
END;
Try this one, Usong ref cursor.
declare
type c is ref cursor;
c2 c;
type rec is record(
id number,
name varchar(20)
);
r rec;
procedure p1(c1 in out c,r1 in out rec)is begin
loop
fetch c1 into r1;
exit when c1%notfound;
dbms_output.put_line(r1.id || ' ' ||r1.name);
end loop;
end;
begin
open c2 for select id, name from student;
p1(c2,r);
end;
Yes you can use Cursor explicitly into procedure and function,for that cursor need to declare into package as variable

Resources