PL/SQL error question - sql

I am trying to write a stored procedure that inserts a row into an employee table. If the department does not exist, that department needs to be inserted into the departments table. I have the following code:
drop table employees;
drop table departments;
create table departments(
dept varchar2(30),
dept_number number,
dept_city varchar2(30),
CONSTRAINT pk_dept PRIMARY KEY(dept)
);
create table employees(
dept varchar2(30),
employee_name varchar2(40),
employee_id number,
CONSTRAINT pk_id PRIMARY KEY(employee_id),
CONSTRAINT fk_dept FOREIGN KEY (dept) REFERENCES departments(dept)
);
CREATE OR REPLACE PROCEDURE employeeadd(
a_dept IN VARCHAR2,
a_employee_name IN VARCHAR2,
a_employee_id IN NUMBER)
as
li_count NUMBER;
BEGIN
sp_check_dept(a_dept, li_count);
if li_count = 0 then
INSERT INTO departments (dept) values (a_dept);
return;
end if;
INSERT INTO employee values (a_dept, a_employee_name, a_employee_id);
end;
/
create or replace procedure sp_check_dept(a_dept IN NUMBER,
a_count OUT NUMBER)
as
begin
select count(*)
into a_count
from departments
where dept_number = a_dept;
end;
/
When I run my execute statement as execute employeeadd('marketing', 'john', 10); I get the following errors. I can't seem to figure out how to get past the errors and/or write this correctly:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at "employeeadd", line 8
ORA-06512: at line 1

Why is li_count declared outside the BEGIN...END block? Do you need to assign it before sending it as an argument to sp_check_dept()?
Edit: Just saw your followup comment: sp_check_dept is expecting a number as its first parameter; you have declared a_dept as VARCHAR.

sp_check_dept takes a department number an input parameter (a NUMBER) and returns a count as an ouput parameter. employeeadd is passing a department name (a VARCHAR2) as the first parameter to sp_check_dept. There are a couple of ways to fix this. In general, you'll want a more consistent method of naming parameters to make it easier to identify these problems.
Option 1: Use the department name for both functions
create or replace procedure sp_check_dept(p_dept_name IN departments.dept%type,
p_count OUT NUMBER)
as
begin
select count(*)
into p_count
from departments
where dept = p_dept_name;
end;
/
CREATE OR REPLACE PROCEDURE employeeadd(
p_dept_name IN departments.dept%type,
p_employee_name IN employees.employee_name%type,
p_employee_id IN employees.employee_id%type)
as
li_count NUMBER;
BEGIN
sp_check_dept(p_dept_name, li_count);
if li_count = 0 then
INSERT INTO departments (dept)
VALUES (p_dept_name);
end if;
INSERT INTO employee(dept, employee_name, employee_id)
VALUES (p_dept, p_employee_name, p_employee_id);
end;
/
Option 2: Convert the department name in employeeAdd to the department number before passing it to sp_check_dept
create or replace procedure sp_check_dept(p_dept_number IN departments.dept_number%type,
p_count OUT NUMBER)
as
begin
select count(*)
into p_count
from departments
where dept_number = p_dept_number;
end;
/
CREATE OR REPLACE FUNCTION get_dept_number( p_dept_name IN departments.dept%tyep )
RETURN departments.dept_number%type
IS
l_dept_number departments.dept_number%type;
BEGIN
SELECT dept_number
INTO l_dept_number
FROM departments
WHERE dept = p_dept_name;
RETURN l_dept_number
END;
/
CREATE OR REPLACE PROCEDURE employeeadd(
p_dept_name IN departments.dept%type,
p_employee_name IN employees.employee_name%type,
p_employee_id IN employees.employee_id%type)
as
li_count NUMBER;
BEGIN
sp_check_dept( get_dept_number(p_dept_name), li_count);
if li_count = 0 then
INSERT INTO departments (dept)
VALUES (p_dept_name);
end if;
INSERT INTO employee(dept, employee_name, employee_id)
VALUES (p_dept, p_employee_name, p_employee_id);
end;
/
A couple of other observations
I removed the RETURN statement from your IF statement in employeeAdd. You almost certainly do not want to exit the procedure after inserting a row into the DEPARTMENTS table before inserting the row into the EMPLOYEE table.
Your table definition used the plural EMPLOYEES. Your procedure used the singular EMPLOYEE. I did not correct that because I wasn't sure whether the DDL you posted was incorrect or whether the procedure you posted was incorrect.
It would, in general, make far more sense for sp_check_dept to be implemented as a function that returned the count rather than as a procedure with an OUT parameter. If a piece of code simply exists to return data to the caller, it should be declared as a function.
From a data model standpoint, the column name DEPT isn't particularly good. It would be far more appropriate to use something like DEPARTMENT_NAME that conveys what the column actually represents.
From a data model standpoint, having the VARCHAR2 column DEPT (even if it is renamed to DEPARTMENT_NAME) as the primary key of DEPARTMENTS and the foreign key in EMPLOYEES does not make much sense. The primary key should be immutable. However the name of the department will change over time. It would make far more sense for the DEPARTMENT_NUMBER to be the primary key and for the DEPARTMENT_NAME to simply be marked as unique. That will make it far easier when the Marketing department gets renamed Advertising in the future because you won't have to chase down all the child tables to update them.
You should pick a naming convention for procedures and stick with that. I would prefer check_dept and add_employee (verb followed by subject, underscores separating words, no prefix). But if you wanted sp_check_dept and sp_add_employee or checkDept and addEmployee or even sp_dept_check and sp_employee_add that would be fine. But you'll drive yourself, and the other developers, crazy if there is no pattern to your procedure naming conventions.

2 possibilities I can see:
1. the employee table has columns in a different order than your insert statement and it's trying to convert dept or name to the id
2. the value set into li_count isn't a number so it's trying to convert the return value to a number and giving you the error

Related

please find the solution of this problem. ( in PL SQL ) --------

I'm doing an assignment and the last question states that I call the procedure from question 2 and function from question 3 and then put them in an insert clause(question 4).
Write a PL/SQL Code for an anonymous block to do the following (in package):
a) Get the doctor name for a given doctor id (read from keyboard)
using a function [FunGetDoctorName].
b) Get the department name for a given department id (read from
keyboard) using a procedure [ProGetepartmentName].
c) Insert the doctor name and department name (which you got from the
function and
procedure) by including serial number and current date into the
visitings table.
put the answer in ( Package)
this is my table codes:
CREATE TABLE DEPT(
DeptNo NUMBER PRIMARY KEY,
DeptName VARCHAR2(30) NOT NULL);
CREATE TABLE DOCTORS(
DoctorID NUMBER PRIMARY KEY,
DoctorNAME VARCHAR2(30) NOT NULL,
DeptNo NUMBER REFERENCES DEPT(DEPTNO),
Salary NUMBER NOT NULL);
CREATE TABLE VISITINGS(
SlNo NUMBER PRIMARY KEY,
DoctorName VARCHAR2(30),
DepartmentName VARCHAR2(30),
VisitDate DATE);
INSERT INTO DEPT VALUES(10,'ENT');
INSERT INTO DEPT VALUES(20,'Orthopedic');
INSERT INTO DEPT VALUES(30,'Cardiology');
INSERT INTO DEPT VALUES(40,'Neurology');
INSERT INTO DOCTORS VALUES(101,'Abheer',20,2550);
INSERT INTO DOCTORS VALUES(102,'Zuwaina',10,2175);
INSERT INTO DOCTORS VALUES(103,'Sara',30,1985);
INSERT INTO DOCTORS VALUES(104,'Fatma',20,2200);
INSERT INTO DOCTORS VALUES(105,'Laila',10,2600);
INSERT INTO VISITINGS VALUES(1,'Sara','Cardiology','10-Nov-19');
INSERT INTO VISITINGS VALUES(2,'Abheer','Orthopedic','11-Nov-19');
My function
create or replace function FunGetDoctorName(Docid number) return varchar2 is
docname DOCTORS.DoctorName%type;
Begin
select DoctorName into docname from DOCTORS where DoctorID = Docid;
return docname;
End ;
/
My procedure
create or replace procedure ProGetDepartmentName is
depname dept.DeptName%type;
Begin
select DeptName into depname from dept where DeptNo =10;
dbms_output.put_line(depname);
End ;
/
here is the problem:
Create or replace package pkg1 is
Function FunGetDoctorName(Docid Number) return varchar2 ;
procedure ProGetDepartmentName(DeptNo NUMBER);
end pkg1;
/
CREATE OR REPLACE PACKAGE BODY pkg1 AS
FUNCTION FunGetDoctorName(Docid Number)
RETURN varchar2 IS
docname DOCTORS.DoctorName%type;
BEGIN
select DoctorName into docname from DOCTORS where DoctorID = Docid;
return docname ;
END;
PROCEDURE ProGetDepartmentName(DeptNo NUMBER) IS
depname dept.DeptName%type;
BEGIN
Select DeptName into depname from dept where DeptNo=10;
dbms_output.put_line(depname) ;
END;
END pkg1 ;
/
declare
ProGetDepartmentName
(:DeptNo in dept.DeptNO%type,
depname in dept.DeptName%type)
FunGetDoctorName
(:Docid in DOCTORS.DoctorID%type ,
docname in DOCTORS.DoctorName%type);
docname varchar2(30);
depname varchar2(30);
Docid number;
serial number;
is
Begin
dbms_output.put_line('Department Name: '||depname);
select count(slno) into serial from visitings;
serial :=serial+1;
insert into visitings(slno,doctorname,departmentname,visitdate) values(serial,docname,depname,sysdate);
End;
/
I keep getting errors :
SP2-0552: Bind variable "DOCID" not declared.
First of all, your procedure must have one in and one out parameter so that you can pass deptno and get deptname as the output.
procedure ProGetDepartmentName(p_depnum number,P_depname out varchar)
is
Begin
select DeptName into P_depname from dept where DeptNo = p_depnum;
dbms_output.put_line(p_depnum);
End ;
/
In your pl/sql block, you can use substitution variable to take an input from keyboard as follows:
declare
V_DeptNo dept.DeptNO%type := &dept_no
V_deptname in dept.DeptName%type;
V_Docid in DOCTORS.DoctorID%type := &doc_id;
is
Begin
Pkg1.ProGetDepartmentName(v_deptno, v_deptname);
dbms_output.put_line('Department Name: '|| v_deptname);
insert into visitings(slno,doctorname,departmentname,visitdate)
values((select count(slno) + 1 from visitings),FunGetDoctorName(v_docid),v_deptname,sysdate);
End;
/
Note:
Instead of fetching count from table and adding one into it for slno, you should use the sequence.
In procedure and function, use exception block to handle no row found or multiple record found or any other issues gracefully.
While inserting in to table, you should also use exceprion block to handle issues gracefully. (In your case it is needed because of the count(slno) + 1 logic as it can assign same number to different sessions executing simentaneously which my lead to primary key violation)

there is few mistake in trigger help me to solve that problem

Create trigger that will
not allow update of salary for employee
who are in sales department.
Emp(no,name,dno,salary,designamtion)
Dept(dno,name,location)
Errors: TRIGGER MYTRIGGER1
Line/Col: 6/11 PLS-00049: bad bind variable 'OLD.EMP'
Line/Col: 6/31 PLS-00049: bad bind variable 'OLD.EMP'
DROP TABLE EMP;
DROP TABLE DEPT;
CREATE TABLE Dept
(
dno NUMBER PRIMARY KEY,
name VARCHAR(15),
location VARCHAR(15) );
CREATE TABLE Emp
(
no NUMBER PRIMARY KEY,
name VARCHAR(15),
dno NUMBER,
salary NUMBER,
designamtion VARCHAR(15),
FOREIGN KEY(dno) REFERENCES Dept(dno) );
insert into DEPT values (1,'SALES','GUJARAT');
insert into DEPT values (2,'MARKETING','UP');
insert into DEPT values (3,'MANUFACTURING','MP');
insert into DEPT values (4,'DEALING','VAPI');
insert into DEPT values (5,'SELL','TAPI');
insert into EMP values (1,'AMAN',2,45400,'MANAGER');
insert into EMP values (2,'BHAMAN',5,20000,'GM');
insert into EMP values (3,'CHAMAN',3,34400,'ADVISOR');
insert into EMP values (4,'DAMAN',4,75400,'WORKER');
insert into EMP values (5,'KHAMAN',1,42400,'MANAGER');
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.emp.no=no and :old.emp.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
You don't need to alias the table the trigger is on. This will compile fine:
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.no=no and :old.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
However, this trigger will fail when you update the table. You cannot select from the table itself in the trigger body. This will raise a mutating table error. Since it is an assignment I'll leave the research up to you.
As Koen said, your code will suffer from mutating table error.
Lucky you - you don't have to select from the table you're just updating (the emp table, right?) - you have everything you need in :new.dno. So:
SQL> create or replace trigger mytrigger1
2 before update of salary on emp
3 for each row
4 declare
5 hmmm varchar2(15);
6 begin
7 select d.name
8 into hmmm
9 from dept d
10 where d.dno = :new.dno;
11
12 dbms_output.put_line('Department name = ' || hmmm);
13 end;
14 /
Trigger created.
Testing:
SQL> set serveroutput on
SQL>
SQL> update emp set salary = 1000 where no = 1;
Department name = MARKETING
1 row updated.
SQL> update emp set salary = 1000;
Department name = MARKETING
Department name = SELL
Department name = MANUFACTURING
Department name = DEALING
Department name = SALES
5 rows updated.
SQL>
Your request is a trigger that disallows certain action for employees in Sales. Your trigger would not do so even it compiled. Two problems:
dbms_output does not prevent anything it merely 'prints' a message.
it prevents any update to salary, except when emp.dno or emp.no is also updated.
First (getting up on soapbox). You are enforcing a business rule. This should not be done in a trigger. It should be done in either the business rules engine of your application or in a database constraint or if you want to be super cautions both. Now a trigger will do the enforcement but business rule violations should glaringly obvious and easy to find, in a trigger becomes a side effect of an action, hidden away and difficult to find. (OK enough of that getting off soapbox).
The prevention method your looking for is raise_application_error. And you want a little code as possible in a trigger. In this case you have everything needed from the EMP table through the pesudo rows :old and :new so there is no reason to join your tables. However you do need to look at the specific department. It is too bad Oracle constrains the Exists predicate to introduction of a sub-select, this would be a perfect place for the structure:
If EXISTS (selects ...) then ...
But that is not available. The following uses a technique of reversing the requirement. That is it selects what is NOT wanted then if found it raise the exception, and if not found it suppress the Oracle raised error. So:
create or replace trigger mytrigger1
before update of salary on emp
for each row
declare
hmmm varchar2(01);
begin
select null
into hmmm
from dept d
where d.dno = :old.dno
and d.name = 'SALES';
raise_application_error(-20199, 'Can not update salary for Employee in Sales.');
exception
when no_data_found then null;
end mytrigger1;
This is sometimes referred to as "Programming the Exception" and is not a generally recommended procedure but at times it is useful. See fiddle here.
There is an outstanding question not addressed here. Can an employee in Sales have a salary change if they are also transferring departments. I.e. should the following succeed or fail?
update emp e
set dno = 2
, salary = salary + 5000
where e.no = 5;

Oracle procedure, placement of multiple records in variables

I'm trying to create my first oracle procedure. The select will return multiple records; I need to be able to place each record in the variables and use the record in later actions in the procedure. Any help please?
key number;
keyCount number;
rub varchar2(50);
srub varchar2(100);
type varchar2(200);
date varchar2(14);
note varchar2(500);
BEGIN
SELECT KEY,COUNT(KEY),RUB,
SRUB,TYPE ,DATE,NOTE FROM Student
WHERE S_KEY = {key};
END;
In PL/SQL we need to select results into matching variables. One way is separate variables for each column (as shown). The alternative is to use a row variable which matches the project of the query; find out more.
You've got an aggregating function - COUNT() so you need a GROUP BY clause which defines the non-aggregating columns. You say you have more than one record so you need to populate a collection not scalar variables. Find out more.
Your procedure should look something like this
create or replace procedure my_first_proc
( p_key in student.s_key%type )
as
type my_rec is record (
key number ,
keyCount number ,
rub varchar2(50); ,
srub varchar2(100) ,
type varchar2(200) ,
date varchar2(14),
note varchar2(500)
);
type my_rec_coll is table of my_rec;
l_student_recs my_rec_coll;
BEGIN
SELECT KEY,COUNT(KEY),RUB,SRUB,TYPE ,DATE,NOTE
bulk collect into l_student_recs
FROM Student
WHERE S_KEY = p_key
group by KEY,RUB,SRUB,TYPE ,DATE,NOTE
;
for idx in l_student_recs.first() .. l_student_recs.last()
loop
-- do some processing here
dbms_output.put_line('RUB = '||l_student_recs(idx).rub);
end loop;
EXCEPTION
when no_data_found then
raise_application_error(-01403, 'no student records for key='||p_key);
END;
Get into good habits:
use sensible variable names
distinguish parameter names from local variables
handle predictable exceptions

Trigger in Oracle SQL: When Timesheet is approved it should update the Payroll Table

When the Table Timesheet is approved (Timesheet_approved* is Not Null) a trigger should fire which will calculate the payment for standard hours due (Payroll_standard*) and the payment due for any overtime (Payroll_overtime* = standard hours X 1.5) for the previous week on the table Payroll.
It should also calculate the pension contribution (Payroll_pension* = 10% of standard + Over time) due and then update the payroll table (working out what the next payroll id would be)
Please note * is used to point out the names of the table attributes.
Tables being used/ affected are Timesheet and Payroll
So far I have the code below. However, I keep getting errors around bad variable bind:
LINE/COL ERROR
-------- -------------------------------------------------------------
32/3 PL/SQL: SQL Statement ignored
33/3 PL/SQL: ORA-01747: invalid user.table.column, table.column, or
column specification
33/3 PLS-00049: bad bind variable 'NEW.PAYROLL_STANDARD'
34/3 PLS-00049: bad bind variable 'NEW.PAYROLL_OVERTIME'
35/3 PLS-00049: bad bind variable 'NEW.PAYROLL_PENSION'
SQL>
SQL:
CREATE OR REPLACE TRIGGER trg_PAYROLLCALC
After UPDATE
on Timesheet FOR EACH ROW
Declare
V_TimesheetHRS number (3);
V_GRADEHRS number (3);
V_TimesheetOT number (3);
v_OTGRADEHRS number (3);
v_payrollID number (3);
BEGIN
SELECT Grade_rate into V_GRADEHRS
FROM Grade join Employee on (Emp_grade = grade_id)
where emp_ID = Timesheet_emp
;
SELECT Timesheet_hours into V_TimesheetHRS
From Funtom_timesheet join Funtom_employee on (emp_ID = Timesheet_emp)
where emp_ID = Timesheet_emp
;
Select Timesheet_OT into V_TimesheetOT
From Timesheet join Employee on (emp_ID = Timesheet_emp)
where emp_ID = Timesheet_emp
;
select Sum(Grade_rate * 1.5) into v_OTGRADEHRS
from Grade join Employee on (Emp_grade = grade_id)
where emp_ID = Timesheet_emp
;
IF Timesheet_approved IS NOT NULL then
Update Payroll set
:new.Payroll_standard := V_GRADEHRS * V_TimesheetHRS;
:new.Payroll_overtime := v_OTGRADEHRS * V_TimesheetOT;
:new.Payroll_pension := ((V_GRADEHRS * V_TimesheetHRS)+(v_OTGRADEHRS * V_TimesheetOT));
END IF;
Select MAX(Payroll_id)+1 into v_payrollID
from Payroll;
:new.Payroll_id := v_payrollID;
END;
/
Notes:
Grade_rate is the standard rate at which the worker is paid,
Grade_id is the PK of that rate,
Emp_grade is the FK that maps on to Grade_id,
Timesheet_emp (FK) maps on to emp_ID,
Payroll_emp (FK) maps on to emp_ID.
I would strongly suggest not to write row level triggers for big business logic.
Row level triggers might have following problem
Trigger will execute for every row , it will be highly in-efficient and slow down the system.
Error handling will be cumbersome , if any one row- triggers has failed/error , then its fails sql update stm & difficult proceed.
very low chance of improving the performance , since trigger execute on row level.
Other developers could NOT find existence of a trigger .
Very difficult to enhance or debug later.
Triggers are difficult to test independently
implement in procedure
join table logically , don’t do row by row
have proper error handling procedure
test procedure independently
you have freedom of calling procedure any part of you code, may be during off peak hours.

How to resolve the component must be declared using oracle Types

I have two oracle types like
create or replace
TYPE T_EMPLOYEE
AS TABLE OF O_EMPLOYEE;
And my O_EMPLOYEE TYPE is
create or replace
TYPE O_EMPLOYEE
AS OBJECT
(
EMP_NAME VARCHAR2(50),
EMP_ID VARCHAR2(50),
EMP_DES VARCHAR2(50)
);
I am using this as an input in a store procedure where i need to check for the validation of name,id and designation. Using following i can convert the table in a select statement.
TABLE(CAST( I_T_EMPLOYEE AS T_EMPLOYEE)) emp,
but I tried to read the value like like T_EMPLOYEE.EMP_NAME, it is saying componenet EMP_NAME must be decalred.
can any one help?
thank you shabilan. There are two ways we can retrieve the value. first one
By writing following in store procedure. you need to declare the variables for
IS
V_EMP_NAME VARCHAR2(50),
V_EMP_ID VARCHAR2(50),
V_EMP_DES VARCHAR2(50),
BEGIN
SELECT PROCESS_TYPE,REQUEST_TYPE,STATUS,EFFECTIVE_DATE_RANGE
INTO V_EMP_NAME, V_EMP_ID, V_EMP_DES
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
--validation part
if(V_EMP_NAME is null or V_EMP_ID or V_EMP_DES ) then RAISE NULL_DATA;
END IF;
This one will fail if you pass multiple Object Types in store procedure like
T_E_I :=T_EMPLOYEE(O_EMPLOYEE('Test','1234','manager'),
O_EMPLOYEE('Test','1234','manager'));
GET_DATA_PKG.EMPLOYEE_ORDER_BY_ROLE(T_E_I, :o_error_code,
:o_error_message);
2nd approach handles the multiple inputs. this is done using cursor.you need to declare the variables for
IS
OEM O_EMPLOYEE := O_EMPLOYEE(null,null,null);
c_EMP_NAME OEM.EMP_NAME%type,
c_EMP_ID OEM.EMP_ID%type,
c_EMP_DES OEM.EMP_DES%type,
Cursor emp_role_curs is
SELECT PROCESS_TYPE,REQUEST_TYPE,STATUS,EFFECTIVE_DATE_RANGE
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
BEGIN
OPEN emp_role_curs;
loop
fetch emp_role_curs into C_EMP_NAME, C_EMP_ID, C_EMP_DES
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
--validation part
if(V_EMP_NAME is null or V_EMP_ID or V_EMP_DES ) then RAISE NULL_DATA;
END IF;
EXIT WHEN emp_role_curs%notfound;
END LOOP;
CLOSE emp_role_curs;
I hope this is useful if some one want to read the object type as a table.