I'm trying to use nested cursor-for-loops and get the following error: "PLS-00225" -> 'C_EMP'.
I know the meaning of it but I don't know how to solve it.
Code:
set serveroutput on
set verify off
set echo off
declare
cursor c_dept is
select department_id, department_name
from departments
where department_id < 100;
cursor c_emp(p_department_id employees.department_id%type) is
select last_name, job_id, hire_date, salary
from employees
where employee_id < 120
and department_id = p_department_id;
begin
<<aussen>>
for r_dept in c_dept loop
dbms_output.put_line('Department Number: ' || r_dept.department_id || chr(10) ||
'Department Name: ' || r_dept.department_name || chr(10));
<<innen>>
for r_emp in c_emp(r_dept.department_id) loop
dbms_output.put_line(c_emp.late_name || c_emp.job_id || c_emp.hire_date || c_emp.salary);
end loop innen;
end loop aussen;
end;
/
In your line:
dbms_output.put_line(c_emp.late_name || c_emp.job_id || c_emp.hire_date || c_emp.salary);
It should be:
r_emp instead of c_emp; and
last_name instead of late_name.
db<>fiddle
Related
Well I have a INDEX BY TABLE which looks like this:
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
And with this Loop I want to fill it:
FOR l IN 1..10
LOOP
deptno := deptno + 10;
SELECT department_id, department_name INTO my_dept_table FROM departments WHERE department_id=deptno;
END LOOP;
Is there a possible way to do that because with my solution it's not working...
Thanks in advice
You can either read each department into the appropriate element of the collection:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
DEPTNO NUMBER;
BEGIN
FOR l IN 1..10
LOOP
deptno := l + 10;
SELECT department_name
INTO my_dept_table(l)
FROM departments
WHERE department_id = deptno;
END LOOP;
FOR I IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
works fine.
But better yet, you can BULK COLLECT the data into your collection, which saves a bunch of code:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
DEPTNO NUMBER;
BEGIN
SELECT DEPARTMENT_NAME
BULK COLLECT INTO MY_DEPT_TABLE
FROM DEPARTMENTS;
FOR I IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
db<>fiddle here
EDIT
OP has indicated that he wants the department number to be used as the index to the collection. In that case I'd use a cursor and add each element separately:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
BEGIN
FOR aRow IN (SELECT DEPARTMENT_ID, DEPARTMENT_NAME
FROM DEPARTMENTS
ORDER BY DEPARTMENT_ID)
LOOP
MY_DEPT_TABLE(aRow.DEPARTMENT_ID) := aRow.DEPARTMENT_NAME;
END LOOP;
FOR I IN MY_DEPT_TABLE.FIRST..MY_DEPT_TABLE.LAST LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
db<>fiddle here
I am working on an assignment and it requires me to place a cursor inside a function.
Now my cursor works fine, but I am having a hard time placing it inside a a function, how can I do this?
The function is supposed to have the same argument as the cursor.
Code:
DECLARE
CURSOR c_emp_salary (v_job_title VARCHAR2)
IS
SELECT employee_id, first_name, salary, min_salary FROM employees e JOIN jobs j ON
e.job_id = j.job_id
AND j.job_title = v_job_title;
v_emp_id employees.employee_id%TYPE;
v_emp_first_name employees.first_name%TYPE;
v_emp_salary employees.salary%TYPE;
v_min_salary jobs.min_salary%TYPE;
BEGIN
OPEN c_emp_salary('Shipping Clerk'); --I WANT THIS VALUE TO BE FROM A FUNCTION.
LOOP
FETCH c_emp_salary INTO v_emp_id, v_emp_first_name, v_emp_salary, v_min_salary;
EXIT WHEN c_emp_salary%NOTFOUND;
dbms_output.put_line('Employee ID: ' || v_emp_id);
dbms_output.put_line('Employee First Name: ' || v_emp_first_name);
dbms_output.put_line('Employee Salary: ' || v_emp_salary);
dbms_output.put_line('Job Min Salary: ' || v_min_salary);
dbms_output.put('Result? ');
IF v_emp_salary = v_min_salary THEN dbms_output.put('Yes');
ELSE dbms_output.put('No');
END IF;
dbms_output.put_line(NULL);
dbms_output.put_line('++++++++++++++++++++++++');
END LOOP;
END;
The assignment description: Write a function that takes job_title and checks all employees in
that job, if an employee has salary equal to min_salary print
“yes” else print “no”. Use a cursor
"Use a cursor" is pointless because every DML or SELECT statement is a cursor.
Anyway, I assume you are looking for this:
CREATE OR REPLACE FUNCTION WRITE_SALARY(v_job_title IN VARCHAR2) AS
CURSOR c_emp_salary IS
SELECT employee_id, first_name, salary, min_salary
FROM employees e
JOIN jobs j ON e.job_id = j.job_id
WHERE j.job_title = v_job_title;
v_emp_id employees.employee_id%TYPE;
v_emp_first_name employees.first_name%TYPE;
v_emp_salary employees.salary%TYPE;
v_min_salary jobs.min_salary%TYPE;
BEGIN
OPEN c_emp_salary;
...
I assume you need something as:
(It needed substitute SCHEMA_NAME and FUNCTION_NAME)
(If return does not needed procedure can be used, and return statements should be removed)
(I don't run this example)
CREATE OR REPLACE function SCHEMA_NAME.FUNCTION_NAME( v_job_title VARCHAR2) return number is
CURSOR c_emp_salary
IS
SELECT employee_id, first_name, salary, min_salary FROM employees e JOIN jobs j ON
e.job_id = j.job_id
AND j.job_title = v_job_title;
v_emp_id employees.employee_id%TYPE;
v_emp_first_name employees.first_name%TYPE;
v_emp_salary employees.salary%TYPE;
v_min_salary jobs.min_salary%TYPE;
result number;
BEGIN
OPEN c_emp_salary('Shipping Clerk'); --I WANT THIS VALUE TO BE FROM A FUNCTION.
LOOP
FETCH c_emp_salary INTO v_emp_id, v_emp_first_name, v_emp_salary, v_min_salary;
EXIT WHEN c_emp_salary%NOTFOUND;
dbms_output.put_line('Employee ID: ' || v_emp_id);
dbms_output.put_line('Employee First Name: ' || v_emp_first_name);
dbms_output.put_line('Employee Salary: ' || v_emp_salary);
dbms_output.put_line('Job Min Salary: ' || v_min_salary);
dbms_output.put('Result? ');
IF v_emp_salary = v_min_salary THEN
dbms_output.put('Yes');
result := 1;
ELSE dbms_output.put('No');
result := 0;
END IF;
dbms_output.put_line(NULL);
dbms_output.put_line('++++++++++++++++++++++++');
return result;
END LOOP;
END;
The OP originally asked:
I am having a hard time placing [the cursor] inside a a function, how can I do this?
and
OPEN c_emp_salary('Shipping Clerk'); --I WANT THIS VALUE TO BE FROM A FUNCTION.
The code below solves the problem originally asked in the question; it is not intended to solve the assignment question that was edited in later as they are not one-and-the-same (that is left as an exercise to the OP).
Oracle Setup:
CREATE TABLE employees (
employee_id NUMBER,
first_name VARCHAR2(50),
salary NUMBER(10,2),
job_id NUMBER
);
CREATE TABLE jobs (
job_id NUMBER,
job_title VARCHAR2(50),
min_salary NUMBER(10,2)
);
INSERT INTO employees VALUES ( 1, 'Alice', 300, 1 );
INSERT INTO jobs VALUES ( 1, 'Shipping Clerk', 200 );
PL/SQL Block:
DECLARE
v_emp_id employees.employee_id%TYPE;
v_emp_first_name employees.first_name%TYPE;
v_emp_salary employees.salary%TYPE;
v_min_salary jobs.min_salary%TYPE;
c_emp_cursor SYS_REFCURSOR;
FUNCTION get_cursor(
v_job_title IN JOBS.JOB_TITLE%TYPE
) RETURN SYS_REFCURSOR
IS
c_emp_salary SYS_REFCURSOR;
BEGIN
OPEN c_emp_salary FOR
SELECT employee_id,
first_name,
salary,
min_salary
FROM employees e
JOIN jobs j
ON ( e.job_id = j.job_id
AND j.job_title = v_job_title );
RETURN c_emp_salary;
END;
BEGIN
c_emp_cursor := get_cursor('Shipping Clerk');
LOOP
FETCH c_emp_cursor INTO v_emp_id, v_emp_first_name, v_emp_salary, v_min_salary;
EXIT WHEN c_emp_cursor%NOTFOUND;
dbms_output.put_line('Employee ID: ' || v_emp_id);
dbms_output.put_line('Employee First Name: ' || v_emp_first_name);
dbms_output.put_line('Employee Salary: ' || v_emp_salary);
dbms_output.put_line('Job Min Salary: ' || v_min_salary);
dbms_output.put('Result? ');
IF v_emp_salary = v_min_salary THEN dbms_output.put('Yes');
ELSE dbms_output.put('No');
END IF;
dbms_output.put_line(NULL);
dbms_output.put_line('++++++++++++++++++++++++');
END LOOP;
END;
/
Output:
Employee ID: 1
Employee First Name: Alice
Employee Salary: 300
Job Min Salary: 200
Result? No
++++++++++++++++++++++++
db<>fiddle here
SET SERVEROUTPUT on
declare
type emp_record is record
(v_empid emp1.empno % type;
v_ename emp1.ename % type; )
emp_rec emp_record;
v_sal emp1.sal%type;
begin
v_sal:=:v_sal;
select empno,ename
into emp_rec
from emp1
where sal:=v_sal;
dbms_output.put_line('The employee whose salary is ' || v_sal || 'has Employee Id ' || emp_rec.v_empid || 'and his name is ' || emp_rec.v_ename);
end;
The error is :
ORA-00922: missing or invalid option
There are several issues within the code. Convert to the one as below:
declare
type emp_record is record
(v_empid emp1.empno % type,
v_ename emp1.ename % type );
emp_rec emp_record;
v_sal emp1.sal%type:=&i_sal;
begin
select empno,ename
into emp_rec
from emp1
where sal = v_sal;
dbms_output.put_line('The employee whose salary is ' || v_sal || 'has Employee Id '
|| emp_rec.v_empid || 'and his name is ' || emp_rec.v_ename);
end;
/
Whenever it prompts for the i_sal, you'll input the desired salary parameter value.
Demo
If you have multiple records to be manipulated as #APC mentioned, such as more than one people has the same salary,you'll need such a loop statement :
declare
type emp_record is record
(v_empid emp1.empno % type,
v_ename emp1.ename % type,
v_sal emp1.sal % type);
emp_rec emp_record;
v_sal emp1.sal%type:=&i_sal;
cursor c_emp is
select empno,ename,sal
into emp_rec
from emp1
where sal = v_sal;
begin
open c_emp;
loop
fetch c_emp into emp_rec;
exit when c_emp%notfound;
dbms_output.put_line('The employee whose salary is ' || emp_rec.v_sal || ' has Employee Id '
|| emp_rec.v_empid || ' and his name is ' || emp_rec.v_ename);
end loop;
close c_emp;
end;
/
Demo
The simplest method to fix that particular problem is to use aggregation:
select max(empno), max(ename)
into emp_rec
from emp1
where sal = v_sal;
An aggregation query with no group by always returns exactly one row.
That said, the more correct method is to handle the exception that is generated by not having a matching value. You will also need to handle exceptions for duplicate values as well.
So here is the code as given to me in my oracle class:
CREATE OR REPLACE PROCEDURE add_job_hist
(p_emp_id IN employees.employee_id%TYPE,
p_new_jobid IN jobs.job_id%TYPE) IS
BEGIN
INSERT INTO job_history
SELECT employee_id, hire_date, SYSDATE, job_id, department_id
FROM employees
WHERE employee_id = p_emp_id;
UPDATE employees
SET hire_date = SYSDATE,
job_id = p_new_jobid,
salary = (SELECT min_salary + 500
FROM jobs
WHERE job_id = p_new_jobid)
WHERE employee_id = p_emp_id;
DBMS_OUTPUT.PUT_LINE('Added employee ' || p_emp_id || '
details to the JOB_HISTORY table');
DBMS_OUTPUT.PUT_LINE('Updated current job of employee ' ||
p_emp_id|| 'to ' || p_new_jobid);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001, 'Employee does not exist!');
END add_job_hist;
/
The problem is that this is not a "SELECT INTO" statement, so the WHEN NO_DATA_FOUND exception isn't processed. I can raise my own user-defined exception, but it doesn't seem to let me do this in a procedure. How can I fix this so that it handles the exception? Because right now, if i give a p_emp_id that does not exist, it handles it anyway and the instructions do not want any additional procedure variables.
Thanks!
Check using SQL%ROWCOUNT after the insert statement, if it returns 0, then raise no_data_found explicitly.
CREATE OR REPLACE PROCEDURE add_job_hist(p_emp_id IN employees.employee_id%TYPE,
p_new_jobid IN jobs.job_id%TYPE) IS
BEGIN
INSERT INTO job_history
SELECT employee_id,
hire_date,
SYSDATE,
job_id,
department_id
FROM employees
WHERE employee_id = p_emp_id;
IF SQL%ROWCOUNT = 0 THEN
RAISE no_data_found;
END IF;
UPDATE employees
SET hire_date = SYSDATE,
job_id = p_new_jobid,
salary =
(SELECT min_salary + 500 FROM jobs WHERE job_id = p_new_jobid)
WHERE employee_id = p_emp_id;
dbms_output.put_line('Added employee ' || p_emp_id || '
details to the JOB_HISTORY table');
dbms_output.put_line('Updated current job of employee ' || p_emp_id || 'to ' || p_new_jobid);
EXCEPTION
WHEN no_data_found THEN
raise_application_error(-20001, 'Employee does not exist!');
END add_job_hist;
/
I'm trying to create a stored procedure as follows:
CREATE OR REPLACE PROCEDURE storedprocedure(emp number) AS
BEGIN
DECLARE
-- create the cursor based on a query
cursor emp_cursor is
select e.employeeid, firstname, lastname, e.departmentid, e.title,
salary, d.departmentname, billrate
from employees e
full join departments d on e.departmentID = d.departmentID
full join employeeproject p on e.employeeID = p.employeeID where e.employeeID = emp;
BEGIN
open emp_cursor;
-- first display information about the employee
dbms_output.put_line('- -');
dbms_output.put_line('- -');
dbms_output.put_line('Employee#' || e.employeeid
|| ' Name:' || TRIM(e.firstname) || ' ' || TRIM(e.lastname)
|| ' Dept: ');
dbms_output.put_line('_________________________________________________________');
dbms_output.put_line('- -');
dbms_output.put_line('- - Title: ' || e.title
|| ' Salary: ' || to_char(e.salary,'$999,999,999.99'));
dbms_output.put_line('- - Billing Rate: ' || to_char(billrate,'$999,999.99'));
-- next call the stored procedure to show department information
END;
END;
/
But it compiles with errors. When I show errors it tells me e.employeeID, e.title, and billrate must all be declared, but they are the original query. What am I doing wrong here? Am I misunderstand what it means to have them declared?
These are all columns that exist within the tables being queried and running the query as SQL gets results.
You are opening the cursor, but you are not fetching it into anything - either a series of scalar variables or a record type - which would normally be done in a loop. Then when you are in the loop you refer to the variables/record, not the table used in the cursor query - that is out of scope outside the cursor declaration.
There's a slightly simpler implicit cursor loop that you might find a bit easier in this case:
CREATE OR REPLACE PROCEDURE storedprocedure(emp number) AS
BEGIN
FOR rec IN (
select e.employeeid, firstname, lastname, e.departmentid, e.title,
salary, d.departmentname, billrate
from employees e
full join departments d on e.departmentID = d.departmentID
full join employeeproject p on e.employeeID = p.employeeID where e.employeeID = emp
)
LOOP
-- first display information about the employee
dbms_output.put_line('- -');
dbms_output.put_line('- -');
dbms_output.put_line('Employee#' || rec.employeeid
|| ' Name:' || TRIM(rec.firstname) || ' ' || TRIM(rec.lastname)
|| ' Dept: ');
dbms_output.put_line('_________________________________________________________');
dbms_output.put_line('- -');
dbms_output.put_line('- - Title: ' || rec.title
|| ' Salary: ' || to_char(rec.salary,'$999,999,999.99'));
dbms_output.put_line('- - Billing Rate: ' || to_char(rec.billrate,'$999,999.99'));
-- next call the stored procedure to show department information
END LOOP;
END;
/
Notice that all the references to columns inside the loop are of the form rec.<field> - they are taken from the record for this time round the loop, not directly from the underlying tables.
You also had a nested block which isn't doing any harm but isn't useful, so I took that out.
If you particularly wanted to use the explicit open-fetch-close cursor handling it would look something like this:
CREATE OR REPLACE PROCEDURE storedprocedure(emp number) AS
-- create the cursor based on a query
cursor emp_cursor is
select e.employeeid, firstname, lastname, e.departmentid, e.title,
salary, d.departmentname, billrate
from employees e
full join departments d on e.departmentID = d.departmentID
full join employeeproject p on e.employeeID = p.employeeID where e.employeeID = emp;
-- record variable based on cursor definition
rec emp_cursor%ROWTYPE;
BEGIN
OPEN emp_cursor;
LOOP
-- fetch the next row result from the cursor into the record vairable
FETCH emp_cursor INTO rec;
-- break out of the loop if there are no more results to fetch
EXIT WHEN emp_cursor%NOTFOUND;
-- first display information about the employee
dbms_output.put_line('- -');
dbms_output.put_line('- -');
dbms_output.put_line('Employee#' || rec.employeeid
|| ' Name:' || TRIM(rec.firstname) || ' ' || TRIM(rec.lastname)
|| ' Dept: ');
dbms_output.put_line('_________________________________________________________');
dbms_output.put_line('- -');
dbms_output.put_line('- - Title: ' || rec.title
|| ' Salary: ' || to_char(rec.salary,'$999,999,999.99'));
dbms_output.put_line('- - Billing Rate: ' || to_char(rec.billrate,'$999,999.99'));
-- next call the stored procedure to show department information
END LOOP;
CLOSE emp_cursor;
END;
/