PL/SQL procedure that should output the following - sql

Just cant get the output correct, maybe a loop problem ?
Have tried a few things but
Just cant get the output correct
should look like
Department 1 SALES managed by Alvin
Total number of employees: 5
00110 Alvin
00103 Ami
00109 Michael
00101 Peter
00107 Wendy
Department 2 ACCOUNTING managed by Alice
Total number of employees: 2
00120 Alice
00125 Angela
Department 3 GAMES managed by Bob
Total number of employees: 2
00150 Bob
00105 Robert
CREATE TABLE Department (
D# NUMBER(5) NOT NULL, /* Department number */
DName VARCHAR2(30) NOT NULL, /* Department name */
Manager# CHAR(5) NOT NULL, /* Department manager number */
MSDate DATE, /* Manager start date */
CONSTRAINT Department_PK PRIMARY KEY(D#),
CONSTRAINT Department_CK UNIQUE(DName)
);
CREATE TABLE Employee (
E# CHAR(5) NOT NULL, /* Employee number */
Name VARCHAR2(30) NOT NULL, /* Employee name */
DOB Date, /* Date of birth */
Address VARCHAR2(50), /* Home address */
Sex CHAR, /* M-Male, F-Female */
Salary NUMBER(7,2), /* Salary */
Super# CHAR(5), /* Supervisor number */
D# NUMBER(5), /* Department number */
CONSTRAINT Employee_PK PRIMARY KEY(E#),
CONSTRAINT Employee_FK1 FOREIGN KEY (Super#) REFERENCES Employee(E#),
CONSTRAINT Employee_FK2 FOREIGN KEY (D#) REFERENCES Department (D#)
);
ARE THE TABLES
THE CODE I HAVE BEEN TRYING IS
CREATE OR REPLACE PROCEDURE
INSERT_MANAGER IS
MANAGER_NAME VARCHAR(40);
DEPT_# NUMBER(5);
DEPT_NAME VARCHAR(40);
EMP_# NUMBER(5);
EMP_NAME VARCHAR(40);
EMP_TOTAL NUMBER(6);
CURSOR MANAGER IS
SELECT Name, Department.D#,DName
INTO MANAGER_NAME, DEPT_#, DEPT_NAME
FROM Employee
JOIN Department
ON
Department.D# = Employee.D#
WHERE E# = Manager#
ORDER BY DEPT_# ASC;
CURSOR EMPLOYEE IS
SELECT COUNT(NAME),NAME,E#
INTO EMP_TOTAL,EMP_NAME,EMP_#
FROM Employee
JOIN Department ON
Department.D# = Employee.D#
WHERE E# = Manager#
GROUP BY NAME,E#;
BEGIN
OPEN MANAGER;
OPEN EMPLOYEE;
LOOP
FETCH MANAGER INTO MANAGER_NAME, DEPT_#, DEPT_NAME;
FETCH EMPLOYEE INTO EMP_TOTAL,EMP_NAME,EMP_#;
EXIT WHEN MANAGER%notfound;
DBMS_OUTPUT.PUT_LINE('Department ' || DEPT_# || ' ' || DEPT_NAME || ' Managed By: ' || MANAGER_NAME );
DBMS_OUTPUT.PUT_LINE('Total Number Of Emploees ' || EMP_TOTAL);
DBMS_OUTPUT.PUT_LINE(EMP_# || ' ' || EMP_NAME);
END LOOP;
CLOSE MANAGER;
END;
/
My current output lhas the correct manager
but the incorrect TOTAL NUMBER OF EMPLOYEES
and is only showing 1 of the Employees under that manager
Department 1 SALES Managed By: Alvin
Total Number Of Emploees 1
150 Bob
Department 2 ACCOUNTING Managed By: Alice
Total Number Of Emploees 1
338 Helmus
Department 3 GAMES Managed By: Bob
Total Number Of Emploees 1
110 Alvin
THANKS IN ADVANCE FOR ANY HELP.
LUKE

The following produces your desired output while for the most part maintaining your logic flow (except for a 2nd loop for the Employee cursor). But I have made a couple syntactical changes.
First, names are important. Attempt to name objects according to the function being preformed. For example the MANAGER cursor really gets nothing about the manager (except for name). What is gets is information about the department; also
renamed the employee cursor (just because 2 objects with the same name - cursor and table). Structurally, I moved getting the employee count to the department cursor (manager) as it pertains to the department not the employee. As far as naming goes the procedure name itself is misleading as the procedure has nothing to do with inserting managers (but I let it); unless of course it's just part of a larger process.
Definition consistence is also important, and there are problems with that here. For example: Look at table column employee.name vs. the variable manager_name. And what's with employee.e# vs. emp_#? A method to stay consistent is to anchor procedure variables to the table columns they represent.
So instead of "manager_name varchar(40)" use "manager_name employee.name%type"
BTW: Varchar is frowned upon use varchar2.
But I left these with your original definitions. (Just for fun I played around this the data values a little - just values not structure.) The result then:
create or replace procedure insert_manager is
-- department cursor variables
manager_name varchar(40);
dept_# number(5);
dept_name varchar(40);
emp_total number(6);
-- employee cursor variables
emp_# number(5);
emp_name varchar(40);
-- cursors definitions
cursor department_cur is
select name, d#, dname, demps
from ( select e.name
, d.d#
, d.dname
, count(e.e#) over (partition by d.d#) demps
, e.e#
, d.manager# m#
from employee e
join department d on d.d# = e.d#
)
where e# = m#
order by d# asc;
cursor employee_cur(ed# integer) is
select e.name, e.e#
from employee e
where e.d# = ed#;
begin
dbms_output.put_line( 'Chipmunks Gaming, LLC: Department Employee List as of ' || to_char(sysdate, 'dd-Mon-yyyy'));
open department_cur;
loop
fetch department_cur into manager_name, dept_#, dept_name, emp_total;
exit when department_cur%notfound;
dbms_output.put_line('Department ' || dept_# || ' ' || dept_name || ' Managed By: ' || manager_name);
dbms_output.put_line('Total Number Of Employees ' || emp_total);
open employee_cur (dept_#);
loop
fetch employee_cur into emp_name, emp_#;
exit when employee_cur%notfound;
dbms_output.put_line(emp_# || ' ' || emp_name);
end loop;
close employee_cur;
end loop;
close department_cur;
end insert_manager;
-- Generate test data
insert into Department (D#, DName, Manager#)
select 1, 'SALES', '00150' from dual union all
select 2, 'ACCOUNTING','00120' from dual union all
select 3, 'GAMES', '00110' from dual ;
insert into employee(E#, Name ,D# )
select '00150', 'Simon' ,1 from dual union all
select '00103', 'Ami' ,1 from dual union all
select '00109', 'Michael' ,1 from dual union all
select '00101', 'Peter' ,1 from dual union all
select '00107', 'Wendy' ,1 from dual union all
select '00120', 'Theodore' ,2 from dual union all
select '00125', 'Angela' ,2 from dual union all
select '00110', 'Alvin' ,3 from dual union all
select '00105', 'Robert' ,3 from dual ;
---------------------------------------------------------------------------------
-- run it
begin
insert_manager;
end;
The next big step would be using Implicit cursors (cursor for loops) instead of Explicit cursors. That would take of Opening, Looping through, and Closing the cursors. I'll leave that for you.
Good Luck. Hope this helps.

Related

Not enough values selecting into a table value

Here is the code:
create type emp_high_sal_ot is object
(
full_name varchar2(64),
phone_number varchar(20),
salary number(10,2)
);
create type emp_high_sal_nt is table of emp_high_sal_ot;
and the following function doesn't compile:
create or replace function get_highest_paid_emps return emp_high_sal_nt
AS
rec emp_high_sal_nt;
avg_sal NUMBER;
begin
SELECT AVG(salary)
INTO avg_sal
FROM employees;
SELECT last_name || ' ' || first_name, phone_number, salary
INTO rec
FROM employees
WHERE salary > avg_sal;
return rec;
end;
It says:
PL/SQL: ORA-00947: not enough values
Why is that?
Why is that?
You are trying to put 3 values (and multiple rows) into a single variable.
Use BULK COLLECT INTO and wrap the values in the object type:
create or replace function get_highest_paid_emps return emp_high_sal_nt
AS
rec emp_high_sal_nt;
avg_sal NUMBER;
begin
SELECT AVG(salary)
INTO avg_sal
FROM employees;
SELECT emp_high_sal_ot(
last_name || ' ' || first_name,
phone_number,
salary
)
BULK COLLECT INTO rec
FROM employees
WHERE salary > avg_sal;
return rec;
end;
/
Given the sample data:
CREATE TABLE employees (first_name, last_name, phone_number, salary) AS
SELECT 'Alice', 'Abbot', '0123456', 100000 FROM DUAL UNION ALL
SELECT 'Betty', 'Baron', '1111111', 250000 FROM DUAL UNION ALL
SELECT 'Carol', 'Chris', '9876543', 300000 FROM DUAL;
Then:
SELECT * FROM TABLE(get_highest_paid_emps());
Outputs:
FULL_NAME
PHONE_NUMBER
SALARY
Baron Betty
1111111
250000
Chris Carol
9876543
300000
db<>fiddle here

Oracle PLSQL problems creating a procedure

I am trying to wrap some SQL into a PLSQL procedure so a user can pass parameters instead of manually editing a WHERE clause, which would give them the potential to break the working code. The SQL code, which I'm porting is embedded in the PROCEDURE with the exception of the INTO clause.
I know in PLSQL in order to SELECT rows there needs to be an INTO clause. After looking around I saw an example, which creates an object and table type. Something I didn't want to do and seems overly complicated to me. If possible I want to keep everything local to the procedure.
I'm also open to perhaps using BULK collect on the access_history table if that would be a more efficient method.
When I try creating the procedure below It doesn't work and this is where I can use some help and PLSQL expertise to guide me in the best direction to produce the desired data.
Secondly, is there a way to use a DEFAULT value to determine the number of rows to be retrieved.
If the procedure is called like this:
EXEC LAST_EMPLOYEE_HISTORY(1) this means get the last 20 (DEFAULT) rows for employee_id=1, where 20 is the default value.
If the procedure is called like this:
EXEC LAST_EMPLOYEE_HISTORY(1, 50) means get the last 50 rows for employee_id=1.
Any help and expertise in explaininf and helping me fix my issues would be greatly appreciated. Thanks in advance to all who answer.
Below is my test CASE.
ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
CREATE OR REPLACE TYPE access_history_obj AS OBJECT(
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(20),
card_num VARCHAR2(10),
location_id NUMBER(6),
location_name VARCHAR2(30),
access_date DATE
);
CREATE OR REPLACE TYPE access_history_table IS TABLE OF access_history_obj;
Create table employees(
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(20),
card_num VARCHAR2(10),
work_days VARCHAR2(7)
);
INSERT INTO employees (
employee_id,
first_name,
last_name,
card_num,
work_days
)
WITH names AS (
SELECT 1, 'John', 'Doe', 'D564311','YYYYYNN' FROM dual UNION ALL
SELECT 2, 'Justin', 'Case', 'C224311','YYYYYNN' FROM dual UNION ALL
SELECT 3, 'Mike', 'Jones', 'J288811','YYYYYNN' FROM dual UNION ALL
SELECT 4, 'Jane', 'Smith', 'S564661','YYYYYNN' FROM dual
) SELECT * FROM names;
CREATE TABLE locations AS
SELECT level AS location_id,
'Door ' || level AS location_name,
CASE round(dbms_random.value(1,3))
WHEN 1 THEN 'A'
WHEN 2 THEN 'T'
WHEN 3 THEN 'T'
END AS location_type
FROM dual
CONNECT BY level <= 5;
create table access_history(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
card_num varchar2(10),
location_id number(4),
access_date date,
processed NUMBER(1) default 0
);
INSERT INTO access_history(
employee_id,
card_num,
location_id,
access_date
)
WITH rws AS (
SELECT 1,'J11111',2,TO_DATE('2021/08/15 08:30:25', 'YYYY/MM/DD HH24:MI:SS') FROM dual UNION ALL
SELECT 1,'J11111',3,TO_DATE('2021/08/15 18:30:35', 'YYYY/MM/DD HH24:MI:SS') FROM dual UNION ALL
SELECT 2,'E11111',2,TO_DATE('2021/08/15 11:20:35', 'YYYY/MM/DD HH24:MI:SS') FROM dual) SELECT * FROM rws;
CREATE OR REPLACE PROCEDURE LAST_EMPLOYEE_HISTORY(
p_employee_id IN NUMBER,
p_rws IN number)
AS
BEGIN
with rws as (
select e.employee_id,
e.first_name,
e.last_name,
e.card_num,
l.location_id,
l.location_name,
a.access_date,
row_number () over
(
partition by e.employee_id
order by a.access_date DESC
) rn
FROM employees e
JOIN access_history a ON a.employee_id = e.employee_id
JOIN locations l ON l.location_id = a.location_id
)
select employee_id,
first_name,
last_name,
card_num,
location_id,
location_name,
access_date INTO access_history_table
from rws
where
employee_id = p_employee_id AND
rn <= p_rws
order by employee_id, access_date desc;
END;
EXEC LAST_EMPLOYEE_HISTORY(1)
Have a cursor as an OUT parameter and use DEFAULT in the signature of the procedure:
CREATE PROCEDURE LAST_EMPLOYEE_HISTORY(
i_employee_id IN EMPLOYEES.EMPLOYEE_ID%TYPE,
i_rws IN PLS_INTEGER DEFAULT 20,
o_cursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN o_cursor FOR
SELECT e.employee_id,
e.first_name,
e.last_name,
e.card_num,
l.location_id,
l.location_name,
a.access_date
FROM employees e
INNER JOIN access_history a
ON a.employee_id = e.employee_id
INNER JOIN locations l
ON l.location_id = a.location_id
WHERE e.employee_id = i_employee_id
ORDER BY access_date DESC
FETCH FIRST i_rws ROWS ONLY;
END;
/
Then in SQL/Plus or SQL Developer:
VARIABLE cur REFCURSOR;
EXECUTE LAST_EMPLOYEE_HISTORY(1, 50, :cur);
PRINT cur;
db<>fiddle here
Note: From Oracle 12, you can the use FETCH FIRST n ROWS ONLY syntax.

Records not showing results

CREATE TABLE manager (
mgrid INT PRIMARY KEY,
fname VARCHAR2(50),
city VARCHAR2(50)
);
The above is the manager table.
& Below is the code I wrote to get records.
DECLARE
TYPE mgr_info IS RECORD (
mgrid manager.mgrid%TYPE,
fname manager.fname%TYPE,
city manager.city%TYPE
);
mgr mgr_info;
id manager.mgrid%TYPE;
name manager.fname%TYPE;
mcity manager.city%TYPE;
BEGIN
SELECT
mgrid,
fname,
city
INTO mgr
FROM
manager
WHERE
city = 'Mumbai';
id := mgr.mgrid;
name := mgr.fname;
mcity := mgr.city;
dbms_output.put_line('Manager ID = '
|| id
|| ' || Manager Name = '
|| name
|| ' || City = '
|| mcity);
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('No Matching Record Found');
WHEN too_many_rows THEN
dbms_output.put_line('More than one matching Record Found');
END;
/
The issue is I am not able to get proper input even though I have a row that is a manager who lives in Mumbai City.
What is I am not doing that I am not able to get proper output.
Below are the inputs for the table:
insert into manager values(101,'mohan','jaipur');
insert into manager values(102,'sohan','surat');
insert into manager values(103,'rohan', 'delhi');
insert into manager values(104,'jagmohan','mumbai');
The table has Mumbai, the select uses mumbai. The case is different. #Littlefoot was right to ask for the INSERT statements, we couldn't have helped without them.
If you want the SELECT to work, you need to fold both table and query to the same case:
WHERE lower(city) = lower('Mumbai')

SQL Using an OBJECT as parameter for an ORDER mthod

I have a TYPE called Employee which has an ORDER method which takes an Employee as a parameter. I have created a table of Employee and inserted some data. I am trying to test this ORDER method using SELECT however i am having difficulty satisfying the parameter. here is my code
CREATE OR REPLACE TYPE Employee AS OBJECT(
EmpID VARCHAR(15),
eName VARCHAR(30),
ePhone NUMBER,
eAddress VARCHAR(15),
ePosition VARCHAR(15),
eHireDt DATE,
salary NUMBER,
ORDER MEMBER FUNCTION orderSalary (e Employee) RETURN NUMBER)
NOT FINAL;
/
CREATE OR REPLACE TYPE BODY Employee AS
ORDER MEMBER FUNCTION orderSalary(e Employee) return number IS
BEGIN
IF(self.salary > e.salary) then
return(1);
ELSIF (self.salary < e.salary) then
return(-1);
ELSE
return(0);
END IF;
END;
END;
/
CREATE TABLE Emp OF Employee (EmpID PRIMARY KEY)
OBJECT IDENTIFIER PRIMARY KEY;
insert into Emp values('001','kabir',6477732272,'Pharmacy','clerk','2016-03-28',2000);
Here is the line that is giving me trouble:
SELECT p.orderSalary(Employee s) FROM Emp p;
All i need to do is test this method to make sure it works, is there any way i can create an instance of Employee to use as a parameter, or perhaps take a row from the Emp table? Thanks!
SELECT p.orderSalary( VALUE( p ) ) FROM Emp p;
Or:
SELECT p.orderSalary( DEREF( REF( p ) ) ) FROM Emp p;
Or:
SET SERVEROUTPUT ON;
DECLARE
e1 EMPLOYEE := NEW EMPLOYEE('001','kabir',6477732272,'Pharmacy','clerk',DATE '2016-03-28',2000);
e2 EMPLOYEE := NEW EMPLOYEE('002','bob',1234567890,'Library','assistant',DATE '2016-03-07',1000);
BEGIN
DBMS_OUTPUT.PUT_LINE( e1.orderSalary( e2 ) );
END;
/
Update - Select different rows:
insert into Emp values('001','kabir',6477732272,'Pharmacy','clerk','2016-03-28',2000);
insert into Emp values('002','bob',1234567890,'Library','assistant',DATE '2016-03-07',1000);
SELECT e1.ename AS name1,
e2.ename AS name2,
e1.orderSalary( VALUE( e2 ) )
FROM Emp e1
CROSS JOIN Emp e2;
Outputs:
NAME1 NAME2 E1.ORDERSALARY(VALUE(E2))
------ ------ -------------------------
kabir kabir 0
kabir bob 1
bob kabir -1
bob bob 0

PL/SQL creating a trigger problems

I have to create a trigger on an Employee table. If an INSERT or UPDATE statement is issued for the Employee table the trigger launches and makes sure that the value of 'salary' field meets the criteria in the job_min_sal table. After trying over and over I got a mutating table error and now am very frustrated and don't know what to do.
JOB_MIN_SALARY TABLE:
JOB VARCHAR2(50) PRIMARY KEY
MIN_SAL NUMBER(7,2) NOT NULL
The JOB_MIN_SAL table is populated with a variety of job titles and salaries. I am confused working with my trigger and wondering if I could get some assistance where to go from here
CREATE OR REPLACE TRIGGER employee_job_salary
BEFORE INSERT OR UPDATE OF SALARY on employee
FOR EACH ROW
DECLARE
v_salary NUMBER;
BEGIN
SELECT minimum_salary
INTO v_salary
FROM job_min_salary
WHERE UPPER(job) = UPPER(:NEW.job);
I know I am really far off I am just looking for help as for what this requires and what steps I need to take to get this. Thanks!
The EMPLOYEE table:
(
EMPLOYEE_ID NUMBER(4)
EMPLOYEE_NAME VARCHAR2(20)
JOB VARCHAR2(50)
MANAGER_ID NUMBER(4)
HIRE_DATE DATE
SALARY NUMBER(9)
COMMISION NUMBER(9)
DEPARTMENT_ID NUMBER(4)
);
i am supposing you are doing something like
comparing new salary with min salary criterion and update only if :new.SALARY >= v_salary
what are you doing if this is not met, are u trapping an exception or just ignoring the error or returning an error code to debug.
post more info
CREATE TABLE job_min_salary
(
job VARCHAR2(50) PRIMARY KEY,
min_sal NUMBER(7,2) NOT NULL
);
INSERT INTO job_min_salary VALUES('CEO','100');
-- 1 rows inserted.
CREATE TABLE employee
(
employee_id NUMBER(4),
employee_name VARCHAR2(20),
job VARCHAR2(50),
manager_id NUMBER(4),
hire_date DATE,
salary NUMBER(9),
commision NUMBER(9),
department_id NUMBER(4)
);
INSERT INTO employee VALUES(1, 'Name', 'CEO', 1, TO_DATE('2000-01-01', 'YYYY-MM-DD'), 80, 80, 1);
-- 1 rows inserted.
CREATE OR REPLACE TRIGGER employee_job_salary
BEFORE INSERT OR UPDATE OF salary ON employee
FOR EACH ROW
DECLARE
v_salary NUMBER(1);
BEGIN
SELECT 1
INTO v_salary
FROM job_min_salary
WHERE UPPER(job) = UPPER(:NEW.job)
AND :NEW.salary >= min_sal;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20999, 'Salary value is too low for given job');
END;
-- TRIGGER EMPLOYEE_JOB_SALARY compiled
SELECT * FROM employee;
-- 1 Name CEO 1 2000-01-01 00:00:00 80 80 1
UPDATE employee
SET salary = 10
WHERE job = 'CEO';
-- ORA-20999: Salary value is too low for given job
UPDATE employee
SET salary = 100
WHERE job = 'CEO';
-- 1 rows updated.
SELECT * FROM employee;
-- 1 Name CEO 1 2000-01-01 00:00:00 100 80 1