Oracle Trigger How to get Inserted value's parameter - oracle

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

Related

ORA-00947:not enough values

guys i am getting error that PL/SQL: ORA-00947: not enough values while running my code.
declare
type e_type is record ( last_name employees.last_name%type,
email employees.email%type,
hire_date employees.hire_date%type,
job_id employees.job_id%type);
type e_list is table of e_type index by pls_integer;
emps e_list;
begin
for x in 100 .. 110 loop
select last_name,email,hire_date,job_id into emps(x) from employees
where employee_id = x ;
---dbms_output.put_line(emps(x).email);
insert into emp(last_name,email,hire_date,job_id) values emps(x);
end loop;
end;
#WilliamRobertson presents an interesting solution (I had actually forgotten that format). However there is still a much simpler solution. That is to use a single sql statement for the select and insert while avoiding the type and variable declarations and the loop altogether:
insert into emp(last_name,email,hire_date,job_id)
select last_name,email,hire_date,job_id
from employees
where employee_id between 100 and 110;
The problem is here:
insert into emp(last_name,email,hire_date,job_id)
values emp(x);
The PL/SQL INSERT Statement Extension where you use a record variable for the values clause does not accept a column list, so it needs to be either
insert into emp values emp(x);
where emp(x) evaluates to a record matching the entire emp row, or else
insert into
( select last_name, email, hire_date, job_id from emp )
values emp(x);
where emp(x) is a record exactly matching the select list.

passing parameter but unable to fetch?

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;

PL/SQL: Executing Procedure

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.

How do I return a table using a procedure that has a cursor in it ? PL/SQL

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

Iterate over a column in PL/SQL

I have a table Emp with EmpID, Empname, Salary and I am trying to do a calculation for each employee. But I am having problems trying to iterate over each emp to do the calculation. I cant use explicit cursors though.
So right now I am just trying to create the list of empIDs:
Declare
aRows Number;
eid emp_ID%TYPE;
Begin
Select Count(*)
Into aRows
from emp;
Select emp_ID
Into eid
From emp;
FOR days IN 1..Tot_Rows
Loop
Dbms_Output.Put_Line(eid);
eid := eid + 1;
End Loop;
END;
But I get the error:
PLS-00320: the declaration of the type of this expression is incomplete or malformed
The simplest way to iterate over the rows in a table in PL/SQL is to do something like
BEGIN
FOR employees IN (SELECT emp_id FROM emp)
LOOP
dbms_output.put_line( employees.emp_id );
END LOOP;
END;
Alternately, you could fetch all the EID values into a PL/SQL collection and iterate over the collection, as in this example
DECLARE
TYPE emp_id_tbl IS TABLE OF emp.emp_id%type;
l_emp_ids emp_id_tbl ;
BEGIN
SELECT emp_id
BULK COLLECT INTO l_emp_ids
FROM emp;
FOR i IN l_emp_ids .FIRST .. l_empnos.LAST
LOOP
dbms_output.put_line( l_emp_ids (i) );
END LOOP;
END;
If your query can return thousands of rows, however, fetching all the data into the collection may use more of the PGA memory than you'd like and you may need to fetch rows in chunks using the LIMIT clause. But that would seem to be getting ahead of ourselves at this point.
Justin Cave has explained how to do it, but to specifically look at the error you got, that was because of this:
eid emp_ID%TYPE;
When using the %TYPE you have to specify the table name as well as the column name:
eid emp.emp_ID%TYPE;
If you were selecting all the columns in the row you could also look at %ROWTYPE.
Your approach was also making two assumptions: that the initial select into eid found the lowest ID, which is by no means guaranteed; and that all the subsequent ID values are sequential. And you're declaring and populating aRows but referring to Tot_Rows.

Resources