I have created a procedure. It is giving an error ( ORA-01422: exact fetch returns more than requested number of rows). As for a specific department_id there are more than one employees. But how to solve this problem ?
Create Procedure PP1
(ID in number, Percent in number, Sal out number, increase_sal out number) IS
Begin
Select salary, salary *(1+percent/100) into sal, increase_sal
From employees
where department_id= id;
DBMS_OUTPUT.PUT_LINE (sal || ' ' || increase_sal);
END;
/
Variable a number
Variable b number
Exec PP1 (100, 10, :a, :b)
Print a b
Thanks,
Kuntal Roy
My guess is that you want something like (untested)
CREATE TYPE num_tbl IS TABLE OF NUMBER;
CREATE PROCEDURE raise_dept_salaries( p_dept_id IN employees.department_id%type,
p_raise_pct IN NUMBER,
p_old_sals OUT num_tbl,
p_new_sals OUT num_tbl )
AS
BEGIN
SELECT salary
BULK COLLECT INTO p_old_sals
FROM employees
WHERE department_id = p_dept_id;
UPDATE employees
SET salary = salary * (1 + p_raise_pct)
WHERE department_id = p_dept_id
RETURNING salary
BULK COLLECT INTO p_new_sals;
END;
Now, splitting things up this way does introduce the possibility that some other session will modify the data between your first SELECT and your UPDATE so this isn't really safe to use in a multi-user environment. Of course, you really wouldn't want to return both the old and the new salaries in the first place since you already know that they are going to be directly related to each other. If you only returned the collection of new salaries, then you would only need a single UPDATE statement and you wouldn't have the race condition.
Related
I'm trying to increase all salaries of the employee table with the following procedure.
The problem is that the salary column is doing something weird, is working like a variable, because is saving the salary of the last employee in the cursor and adding up to the next employee, so, at the end I got an error
ORA-01438: value larger than specified precision allowed for this column
PROCEDURE increase_salaries AS
v_emp NUMBER;
v_sal NUMBER;
BEGIN
FOR r1 IN cur_emps LOOP
v_emp := r1.employee_id;
v_sal := r1.salary;
UPDATE employees_copy
SET
salary = salary + salary;
COMMIT;
-- salary = salary + salary * v_salary_increase_rate;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
print('Error in employee '||v_emp);
END increase_salaries;
Thanks
I knwo that I can use first a SELECT INTO for the actual salary and re-initialized it to 0, but I saw many examples on internet using UPDATE salary = salary + ... and it works but with my code does not work.
If you have a table with:
CREATE TABLE employees_copy (
salary NUMBER(6,2)
);
Then you can put values from -9999.99 to 9999.99 into the column. If you try to set a value that is outside those bounds then you will get the error:
ORA-01438: value larger than specified precision allowed for this column
You can either:
Use a smaller value; or
Increase the precision of the column using:
ALTER TABLE employees_copy MODIFY (salary NUMBER(7,2));
and then you can put values from -99999.99 to 99999.99 into the column.
However, you probably also need to fix your procedure to only update a single employee at each loop iteration (rather than updating all employees every loop iteration):
PROCEDURE increase_salaries AS
BEGIN
FOR r1 IN cur_emps LOOP
UPDATE employees_copy
SET salary = 2 * salary;
WHERE employee_id = r1.employee_id;
END LOOP;
END increase_salaries;
/
Note 1: Don't catch the OTHERS exception. Let the exception happen and then you can debug it.
Note 2: Don't COMMIT in the procedure; if you do then you cannot chain together multiple procedures and ROLLBACK them all if one fails. Instead, COMMIT from wherever you are calling the procedure.
I have a question while studying Oracle Database's triggers.
First of all, I created two tables like below:
**[Employee]**
(empno NUMBER, VARCHAR2 (20), deptno NUMBER, salary NUMBER)
**[Department]**
(deptno NUMBER, dname VARCHAR2 (20))
Employee deptno refers to Department's deptno.
The trigger I want to create is to calculate the salary average of the data with deptno, such as inserted deptno.
For example, suppose I run the following code.
INSERT INTO employee values (1000, 'PAUL', 10, 4000);
Then Trigger would like to have the following output:
Department [10]'s average of Salary = <avg (salary)>
But I do not know how to pass inserted row's parameter in the Trigger.
Does anyone have a good way for me?
I realized that if I use the AFTER INSERT keyword in the trigger, I get an 'ORA-04091' error because the contents of the table are changing. So I found out that I had to use before insert to get the result.
This is not the correct way, but I was able to express the output I wanted.
It was very simple. Before the table is changed (BEFORE INSERT Keyword), I store the sum of the columns in the variable to get the average.
Then add the new value to the variable using the ': NEW' keyword.
When I used this method, I did not get the 'ORA-04091' error because the table is still before the change.
Thanks to a lot of people, I've found that this is not a good way to write in a trigger.
However, I am thinking about this problem and I have learned a lot of internal actions such as the cost of triggers and when to call them.
Thank you again for all the answerer.
This makes no sense to be implemented as a trigger. This is just a simple SQL query :
select deptno ,avg(salary)
from employee
group by deptno
Trigger is not the best place for such things, but it can be done with compound trigger:
create or replace trigger avg_dept_salary for insert on employee compound trigger
type tb_num is table of number;
v_depts tb_num := tb_num();
v_sum number;
before each row is
begin
if :new.deptno not member of v_depts then
v_depts.extend();
v_depts(v_depts.count()) := :new.deptno;
end if;
end before each row;
after statement is
begin
for i in 1..v_depts.count() loop
select avg(salary) into v_sum from employee where deptno = v_depts(i);
dbms_output.put_line('Department '||v_depts(i)||' average salary: '||v_sum);
end loop;
end after statement;
end;
Test:
insert into employee
select 1001, 'PAUL', 10, 1000 from dual union all
select 1002, 'ANNE', 10, 1700 from dual union all
select 1003, 'MARY', 10, 900 from dual union all
select 1004, 'JOHN', 20, 1200 from dual;
Output for each affected department:
Department 10 average salary: 1900
Department 20 average salary: 1200
how to resolve this one, unable to fetch ?
I am passing the IN parameter but still unable to fetch.
create or replace procedure p1(p_ename in varchar2,p_sal out number)is
begin
select salary into p_sal from employees where last_name=p_ename;
dbms_output.put_line(p_sal);
end;
variable b number;
execute p1('King',:b);
[info](https://infoallsite.wordpress.com/2016/01/29/unable-to-fetch- data)
[error][1]
' got error ,but only one row has last_name as King, '
how to resolve I want to get the salary of king.
[1]: http://i.stack.imgur.com/AsOHG.png'
You table probably has more than 1 rows for name King.
Run this
select count(*) from employees where last_name='King';
If it returns more than 1 row, then you have to choose which row do you need to select. If you want any row at random, then use this select in your procedure.
select salary into p_sal from employees
where last_name=p_ename
and rownum<2;
This is my Function
create or replace
function sal_incr
(
p_grade number)
return number
is
v_inc number;
begin
select raise_percent into v_inc from sal_inc where grade_id = p_grade;
return 1 + (v_inc/100);
end;
This is my procedure:
create or replace
procedure sal_increm
is
begin
UPDATE emp_task SET sal = sal * sal_incr(grade_id);
end;
how to do that package.. without using triggers how to update "old sal","modified by" and "modified on" in separate table
You can have multiple DML statements in a procedure; they'd be rather less useful if you couldn't. You can do a single insert into your history table based on the data in the task table, adding the executing user with the USER function and and the current time with SYSDATE.
create or replace
procedure sal_increm
is
begin
insert into emp_sal_history (empno, old_sal, modified_by, modified_on)
select empno, sal, user, sysdate
from emp_task;
update emp_task set sal = sal * sal_incr(grade_id);
end;
/
If you want to record the new salary as well you can calculate that in the insert too.
This will record a history record even for employees whose grade doesn't get an increment. If you have those or want to handle that possibility and exclude them, you can add
where sal_incr(grade_id) != 1
You could add that to the update as well.
I have a procedure that is given one value as input, after doing some process, using a cursor within that procedure, i want the procedure to return a table.
this one value that is given as an input_param is a non unique ID that is used to identify multiple rows.
so that way I can run a command like :
select * from {call(procedure_name(input_Param)}
My knowledge of PLSQL is limited.
I'm not sure if a procedure can have a a cursor definition inside it, and if that is possible then how do i return an entire Table from the procedure.
BTW: This procedure has to be called using a select statement, and if not a select statement then it should return a table, with a select * at the end.
If I have to specify whih columns to output instead of select * do I need to provide all those column names as input_Params ? ie. If I want the procedure to return only a small number of columns what do I do?
Thanks
You need to use PIPELINED function. Example below, link to more information at the end.
CREATE TABLE test_pipe (
id NUMBER,
name VARCHAR2(20),
salary NUMBER
);
INSERT INTO test_pipe VALUES (1, 'Smith', 5000);
INSERT INTO test_pipe VALUES (2, 'Brown', 8000);
INSERT INTO test_pipe VALUES (3, 'Bay', 10000);
COMMIT;
CREATE TYPE t_pipe_row_test AS OBJECT (
name VARCHAR2(20),
salary NUMBER
);
/
CREATE TYPE t_pipe_test_tab IS TABLE OF t_pipe_row_test;
/
CREATE OR REPLACE FUNCTION test_func_pipe(p_min_salary IN NUMBER)
RETURN t_pipe_test_tab
PIPELINED
AS
BEGIN
FOR v_rec IN (SELECT name, salary
FROM test_pipe
WHERE salary >= p_min_salary)
LOOP
PIPE ROW (t_pipe_row_test(v_rec.name, v_rec.salary));
END LOOP;
END;
/
SELECT * FROM TABLE(test_func_pipe(6000));
Output:
NAME SALARY
-------------------- ----------
Brown 8000
Bay 10000
More about pipelined functions by Tim Hall