Issue when creating a stored procedure - sql

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

Related

Cursor and procedure/function pl sql to display employees name

I want to create a procedure/function, when user input a year and a department name, i need to display the employees' name (first and last name). This is my code so far. Any idea? Thanks!
DECLARE
CURSOR emp_cur (my_date int,my_dept departments.department_name%type) IS
SELECT e.first_name,e.last_name
FROM employees e
join departments d
on e.department_id=d.department_id
where d.department_name=my_dept and my_date=extract(year from e.hire_date);
PROCEDURE emp_excesspaid IS
v_first employees.first_name%TYPE;
v_last employees.last_name%TYPE;
BEGIN
LOOP
FETCH emp_cur INTO v_first, v_last;
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_first||' '||v_last);
END LOOP;
END;
end;
You can make it simple as follows:
CREATE OR REPLACE PROCEDURE PROC_NAME(
P_IN_DEPT_NAME IN VARCHAR2,
P_IN_YEAR IN NUMBER
) AS
BEGIN
For D in (SELECT e.first_name, e.last_name
FROM employees e
join departments d
on e.department_id = d.department_id
where d.department_name = P_IN_DEPT_NAME
and extract(year from e.hire_date) = P_IN_YEAR)
LOOP
DBMS_OUTPUT.PUT_LINE(D.FIRST_NAME || ' ' || D.LAST_NAME);
END LOOP;
END;
/

nested cursor-for-loops

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

Oracle: How to wrap a Cursor inside A Function?

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

How to resolve the missing or invalid option in oracle database?

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.

PL SQL Query for grouping Department names

I am new to PL/SQL and I am stuck at this one point where I am unable to group data by department names. my query is as follows
DECLARE
CURSOR dept_cur IS
select department_name , NVL(employee_name , 'N/A') , NVL(employee.JOB , 'N/A') , NVL(to_char(hire_date, 'DD-MON-YYYY') , 'N/A')
FROM department FULL OUTER JOIN employee ON department.department_id = employee.department_id
order by department_name , employee_name;
v_department_name department.department_name%TYPE;
v_employee_name employee.employee_name%type;
v_employee_job employee.job%type;
v_hire_date VARCHAR(20);
BEGIN
OPEN dept_cur;
fetch dept_cur into v_department_name , v_employee_name , v_employee_job , v_hire_date ;
IF dept_cur%FOUND THEN
DBMS_OUTPUT.PUT_LINE('DEPARTMENT_NAME EMPLOYEE NAME , EMPLOYEE JOB , HIRE DATE');
DBMS_OUTPUT.PUT_LINE('-----------------------------------------------------');
WHILE dept_cur%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(lpad(v_department_name, 11) || lpad(v_employee_name, 16) || lpad(v_employee_job, 22)|| lpad(v_hire_date,15));
fetch dept_cur into v_department_name , v_employee_name , v_employee_job , v_hire_date ;
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('No output.');
END IF;
CLOSE dept_cur;
END;
I want the output to be something like this , but my query returns the entire list .
The number of employees in ACCOUNTING is 2
EMPLOYEE_NAME JOB HIRE DATE
--------------------------------------------------------------
JONES PUBLIC ACCOUNTANT 02-APR-81
STEEL PUBLIC ACCOUNTANT 02-MAR-83
and the same for other departments as well. I understand its a minor change in the PL SQL block logic , but I am unable to figure it out.
DECLARE
CURSOR dept_cur IS
SELECT department_name,
COUNT(employee.employee_id) OVER (PARTITION BY department_name),
NVL(employee_name, 'N/A'),
NVL(employee.JOB, 'N/A'),
NVL(to_char(hire_date, 'DD-MON-YYYY'), 'N/A')
FROM department
FULL OUTER JOIN employee ON department.department_id = employee.department_id
ORDER BY department_name, employee_name;
v_department_name department.department_name%TYPE;
last_department_name department.department_name%TYPE;
v_department_count NUMBER;
v_employee_name employee.employee_name%TYPE;
v_employee_job employee.job%TYPE;
v_hire_date VARCHAR(20);
BEGIN
OPEN dept_cur;
FETCH dept_cur INTO v_department_name, v_department_count, v_employee_name, v_employee_job, v_hire_date;
last_department_name := NULL;
IF dept_cur%FOUND THEN
WHILE dept_cur%FOUND LOOP
IF last_department_name IS NULL OR last_department_name <> v_department_name THEN
DBMS_OUTPUT.PUT_LINE('The number of employees in ' || v_department_name || ' is ' || to_char(v_department_count));
DBMS_OUTPUT.PUT_LINE('EMPLOYEE NAME EMPLOYEE JOB HIRE DATE');
DBMS_OUTPUT.PUT_LINE('--------------------------------------------------------------');
last_department_name := v_department_name;
END IF;
DBMS_OUTPUT.PUT_LINE(rpad(v_employee_name, 24) || rpad(v_employee_job, 24)|| lpad(v_hire_date,14));
FETCH dept_cur INTO v_department_name, v_department_count, v_employee_name, v_employee_job, v_hire_date;
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('No output.');
END IF;
CLOSE dept_cur;
END;
It uses an analytic function COUNT() OVER () to count the number of employees in the result for each department, and inserts that on every row for that department.
Since the query is already ordered by department, it just checks for when that changes to another value, and then prints the headers.