Update table inside a loop in a procedure pl/sql - sql

So i want to hire a new manager for a department. This procedure has 2 parameters, the department name i want to change, and the new id for manager (taken from employee's id). So to change it, i need to update all employee that has the old manager id, and change their manager id into the new one. This is my code so far, the problem is that it updated all of the employees' manager, so the whole database is updated. I must use procedure, not function. Any idea? Thanks.
CREATE OR REPLACE PROCEDURE update_manager(v_deptname IN departments.department_name%TYPE,v_empid IN employees.employee_id%TYPE) IS
v_deptid departments.department_id%type;
BEGIN
SELECT department_id INTO v_deptid FROM departments WHERE department_name=v_deptname;
FOR i IN (SELECT * FROM employees)
LOOP
IF i.department_id=v_deptid THEN
UPDATE employees
SET manager_id=v_empid
WHERE i.department_id=v_deptid;
END IF;
END LOOP;
END;
/
BEGIN
update_manager('Marketing',100);
END;
/

If you must create a PL/SQL procedure then you can do
CREATE OR REPLACE PROCEDURE update_manager(v_deptname IN departments.department_name%TYPE,v_empid IN employees.employee_id%TYPE) IS
v_deptid departments.department_id%type;
BEGIN
SELECT department_id INTO v_deptid FROM departments WHERE department_name=v_deptname;
UPDATE employees
SET manager_id=v_empid
WHERE department_id=v_deptid;
END;
/
The problem with your code that's causing "it updated all of the employees' manager" is that your update statement:
UPDATE employees
SET manager_id=v_empid
WHERE i.department_id=v_deptid;
Your filter here is comparing i.department_id, this is the variable that's coming from your FOR i IN (SELECT * FROM employees), NOT from the update statement. You've already confirmed that i.department_id=v_deptid because you are calling this in a loop with an if statement checking it.
It is not efficient at all to get all rows in employees, loop the results, checking each row if it matches a condition and then firing off an update statement (even if your update statement is filtering against the correct row.

I don't have your tables, but I do have Scott's so - here's an example.
Departments and employees in department 10:
SQL> select * From dept order by deptno;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7839
10 7839 KING
10 7934 MILLER 7782
Procedure that looks like yours (as you're studying PL/SQL), using a variable to fetch department number into, as well as a loop:
SQL> create or replace procedure update_manager
2 (v_deptname in dept.dname%type,
3 v_empno in emp.empno%type
4 )
5 is
6 v_deptno dept.deptno%type;
7 begin
8 select d.deptno
9 into v_deptno
10 from dept d
11 where d.dname = v_deptname; --> this is what you are missing
12
13 for cur_r in (select e.empno
14 from emp e
15 where e.deptno = v_deptno
16 )
17 loop
18 update emp a set
19 a.mgr = v_empno
20 where a.empno = cur_r.empno;
21 end loop;
22 end;
23 /
Procedure created.
Testing: let's modify manager of all employees in department 10:
SQL> exec update_manager ('ACCOUNTING', 7839);
PL/SQL procedure successfully completed.
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7839
10 7839 KING 7839
10 7934 MILLER 7839
SQL>
Seems to work. Though, that's inefficient; doing it row-by-row (in a loop) is usually slow-by-slow. Work on the whole sets of data, rather. Something like this:
SQL> rollback;
Rollback complete.
SQL> create or replace procedure update_manager
2 (v_deptname in dept.dname%type,
3 v_empno in emp.empno%type
4 )
5 is
6 begin
7 update emp e set
8 e.mgr = v_empno
9 where e.deptno = (select d.deptno
10 from dept d
11 where d.dname = v_deptname
12 );
13 end;
14 /
Procedure created.
SQL> exec update_manager ('ACCOUNTING', 7782);
PL/SQL procedure successfully completed.
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7782
10 7839 KING 7782
10 7934 MILLER 7782
SQL>

Why not a simple select for manager id and an update on all concerned employee ?
DECLARE #department_id uniqueidentifier
SELECT #department_id = departement_id
FROM departments
WHERE department_name = #v_deptname
UPDATE employees
SET manager_id = #v_empid
WHERE department_id = #department_id

Related

Make a Report that Can Edit another Report in APEX

I have multiple report pages. I am working on a system that stores a lot of data. Is it possible to have reports that are able to have column edited on two different reports?
i.e. Not all the information may be relevant to the person entering data on the interactive grid. So I would have a condensed version of the report where the would be able to enter the data you are responsible for. But the report would still display the information entered on the condensed report version.
I'd think of a view and its instead of trigger.
Here's an example; see if you can adjust it to interactive grid. It is based on Scott's sample EMP and DEPT tables.
First, a view that joins these two tables:
SQL> create or replace view v_emp_dept as
2 select d.deptno, d.dname, e.empno, e.ename, e.job, e.sal
3 from emp e join dept d on d.deptno = e.deptno;
View created.
Instead of trigger; it fires when you update the view, but in the background it modifies data in view's underlying tables:
SQL> create or replace trigger trg_iu_ved
2 instead of update on v_emp_dept
3 for each row
4 begin
5 update dept d set
6 d.dname = :new.dname
7 where d.deptno = :new.deptno;
8
9 update emp e set
10 e.ename = :new.ename,
11 e.job = :new.job,
12 e.sal = :new.sal
13 where e.empno = :new.empno;
14 end;
15 /
Trigger created.
OK, let's test it. Sample data:
SQL> select * from v_emp_dept where deptno = 10;
DEPTNO DNAME EMPNO ENAME JOB SAL
---------- -------------- ---------- ---------- --------- ----------
10 ACCOUNTING 7782 CLARK MANAGER 2818
10 ACCOUNTING 7839 KING PRESIDENT 5750
10 ACCOUNTING 7934 MILLER CLERK 1495
Update some values:
SQL> update v_emp_dept set
2 dname = 'Accounting',
3 ename = initcap(ename),
4 sal = sal * 10
5 where deptno = 10;
3 rows updated.
Oracle says that 3 rows were updated:
SQL> select * from v_emp_dept where deptno = 10;
DEPTNO DNAME EMPNO ENAME JOB SAL
---------- -------------- ---------- ---------- --------- ----------
10 Accounting 7782 Clark MANAGER 28180
10 Accounting 7839 King PRESIDENT 57500
10 Accounting 7934 Miller CLERK 14950
The view looks OK. What about EMP and DEPT tables?
SQL> select * from dept where deptno = 10;
DEPTNO DNAME LOC
---------- -------------- -------------
10 Accounting NEW YORK
SQL> select empno, ename, job, sal from emp where deptno = 10;
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7782 Clark MANAGER 28180
7839 King PRESIDENT 57500
7934 Miller CLERK 14950
SQL>
Right; data has been changed in these two tables as well.

How to execute table stored query using stored procedure

I have following table structure.
CREATE TABLE "DADMIN"."DATATEMPLATES"
(
"ID" VARCHAR2(100 BYTE),
"QUERY" CLOB,
"ACTIVESTATUS" VARCHAR2(2 BYTE),
)
I'm storing query data like this SELECT CD.CIF , CD.CREDIT_ACCOUNT FROM CUTOMERDATA CD WHERE ID = :PARA_ID
Currently I'm executing it through the C# raw sql execution
Instead of that, I need to execute the table query through the stored procedure. How can I execute table stored query using SP? and return its data using cursor output?
Update:
This is my sample SP,
PROCEDURE Get_customer_data (p_Query_id IN VARCHAR2,
p_cursor OUT OUTPUTCURSOR)
IS
BEGIN
DECLARE
l_query CLOB;
BEGIN
SELECT query
INTO l_query
FROM querytemplates
WHERE id = p_Query_id ;
OPEN p_cursor FOR l_query;
END;
END;
But this makes error
ORA-06512: at line 1
01008. 00000 - "not all variables bound"
And my other problem is, the table stored query also excepting parameter called PARA_ID how can I pass that.
sample table stored query as follows,
SELECT CD.CIF , CD.CREDIT_ACCOUNT FROM CUTOMERDATA CD WHERE ID = :PARA_ID
One option is to return refcursor. Here's an example.
Sample data (that stores queries):
SQL> SELECT * FROM datatemplates;
ID QUERY A
---------- ------------------------------------------------------------ -
1 select deptno, dname from dept A
2 select d.dname, e.ename, e.job, e.sal from emp e join dept d A
on d.deptno = e.deptno
SQL>
Procedure - based on passed ID parameter - selects appropriate query and opens a refcursor based on that select statement:
SQL> CREATE OR REPLACE FUNCTION f_test (par_id IN NUMBER)
2 RETURN SYS_REFCURSOR
3 IS
4 l_query CLOB;
5 l_rc SYS_REFCURSOR;
6 BEGIN
7 SELECT query
8 INTO l_query
9 FROM datatemplates
10 WHERE id = par_id;
11
12 OPEN l_rc FOR l_query;
13
14 RETURN l_rc;
15 END;
16 /
Function created.
Testing:
SQL> SELECT f_test (1) FROM DUAL;
F_TEST(1)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO DNAME
---------- --------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
Some more testing:
SQL> SELECT f_test (2) FROM DUAL;
F_TEST(2)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME ENAME JOB SAL
-------------- ---------- --------- ----------
ACCOUNTING CLARK MANAGER 2450
ACCOUNTING KING PRESIDENT 5000
ACCOUNTING MILLER CLERK 1300
RESEARCH JONES MANAGER 2975
RESEARCH FORD ANALYST 3000
RESEARCH ADAMS CLERK 1100
RESEARCH SMITH CLERK 800
RESEARCH SCOTT ANALYST 3000
SALES WARD SALESMAN 1250
SALES TURNER SALESMAN 1500
SALES ALLEN SALESMAN 1600
SALES JAMES CLERK 950
SALES BLAKE MANAGER 2850
SALES MARTIN SALESMAN 1250
14 rows selected.
SQL>

SQL: How to use functions/operations in a where condition [duplicate]

This question already has answers here:
How do I do top 1 in Oracle? [duplicate]
(9 answers)
Closed last year.
select * from mytable
where date = max(date)
This does not work. I hope my motive is clear. I know I can come around this problem by using sub-queries but is it possible to it without sub-query?
Subquery it is, unless you create your own function which can then be used directly in the WHERE clause. Otherwise, I don't think you can do that.
For example:
SQL> select ename, hiredate from emp order by hiredate desc;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983 --> that's the one we want
SCOTT 09.12.1982
MILLER 23.01.1982
FORD 03.12.1981
JAMES 03.12.1981
<snip>
The way you already know can be improved as following (why is it improved? Because it scans the table only once; your way, I presume, does it twice - once in a subquery (to find the MAX date value), and then once again in the main query):
SQL> with temp as
2 (select e.*,
3 rank() over (order by e.hiredate desc) rnk
4 from emp e
5 )
6 select ename, hiredate
7 from temp
8 where rnk = 1;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983
(Option which isn't that good):
SQL> select ename, hiredate
2 from emp
3 where hiredate = (select max(hiredate) from emp);
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983
SQL>
Or, with your own function:
SQL> create or replace function f_maxhire return date is
2 retval date;
3 begin
4 select max(hiredate) into retval from emp;
5 return retval;
6 end;
7 /
Function created.
SQL> select ename, hiredate
2 from emp
3 where hiredate = f_maxhire;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983
SQL>

Function that returns a query

This is the function :
create or replace function searchbyid (depto in departments.department_id%type)
return sys_refcursor
is result_s sys_refcursor;
begin
open result_s for
SELECT
a.employee_id,
a.first_name,
a.last_name,
a.phone_number,
a.salary,
b.department_id,
b.end_date
FROM
employees a
FULL JOIN job_history b ON a.department_id = b.department_id
WHERE b.department_id = depto;
return result_s;
end;
I'm getting the output in a single line, is it possible to get as a table?
FUNCTION OUTPUT :
Yes, I use SQL Developer
One option is to use ref cursor (as Abra commented). I don't have your tables so I used Scott's.
SQL> create or replace function searchbyid (par_deptno in dept.deptno%type)
2 return sys_refcursor
3 is
4 rc sys_refcursor;
5 begin
6 open rc for
7 select d.deptno, d.dname, e.ename, e.job, e.sal
8 from emp e join dept d on d.deptno = e.deptno
9 where d.deptno = par_deptno;
10
11 return rc;
12 end;
13 /
Function created.
SQL> select searchbyid(10) result from dual;
RESULT
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO DNAME ENAME JOB SAL
---------- -------------- ---------- --------- ----------
10 ACCOUNTING CLARK MANAGER 2450
10 ACCOUNTING KING PRESIDENT 5000
10 ACCOUNTING MILLER CLERK 1300
SQL>
Another option is a pipelined function:
Types first:
SQL> create or replace type t_row as object
2 (deptno number,
3 dname varchar2(10),
4 ename varchar2(10),
5 job varchar2(10),
6 sal number
7 );
8 /
Type created.
SQL> create or replace type t_tab is table of t_row;
2 /
Type created.
Function:
SQL> create or replace function searchbyid (par_deptno in dept.deptno%type)
2 return t_tab pipelined
3 is
4 begin
5 for cur_r in (select d.deptno, d.dname, e.ename, e.job, e.sal
6 from emp e join dept d on d.deptno = e.deptno
7 where d.deptno = par_deptno
8 )
9 loop
10 pipe row (t_row(cur_r.deptno, cur_r.dname, cur_r.ename, cur_r.job, cur_r.sal));
11 end loop;
12 return;
13 end;
14 /
Function created.
SQL> select * from table(searchbyid(10));
DEPTNO DNAME ENAME JOB SAL
---------- ---------- ---------- ---------- ----------
10 ACCOUNTING CLARK MANAGER 2450
10 ACCOUNTING KING PRESIDENT 5000
10 ACCOUNTING MILLER CLERK 1300
SQL>
[EDIT, after you edited the question]
Screenshot shows how data look like when function returns ref cursor in SQL Developer. If you want to see it nicely, scroll far right and click the pencil icon; something like this:
(click on (1) to get result as (2))

Creating procedure with dynamic SQL

Here are the instructions:
A company wants to allow customers to do product search by selecting a product name or
description, and then typing a search term. Using native dynamic SQL, create a
procedure name SEARCH_SP that returns a product name, description, and price base
on users’ search criteria. The procedure needs handle multiple rows being returned.
Here is the code I have so far.
CREATE OR REPLACE PROCEDURE search_sp (product_name IN VARCHAR2,
description IN VARCHAR2,
price_based IN NUMBER
)
AS
BEGIN
SELECT customer.product.name, customer.description, customer.price
FROM dbo.customer
WHERE customer.description = #SEARCH.customer.product.name = #SEARCH
END;
/
EXECUTE IMMEDIATE plsql_block
USING IN OUT new_product_name, new_description, new_price_based;
END;
/
I'm getting compilation errors and more. Any help or suggestions will be greatly appreciated.
Dynamic SQL? What for? To make your life more miserable than it should be? What's wrong with a straight SQL?
I'd suggest a function. Why? You're supposed to return the result. If it is a procedure, it has to have an OUT parameter. If that's so, then it's a function.
Here's how I'd do it; see how it works, use it (and improve) if you want. I don't think I'll get involved into anything dynamic here, sorry.
As I don't have your tables, I'll use Scott's sample schema. Here's data I'm interested in:
SQL> select d.dname, e.ename, e.job, e.sal
2 from dept d join emp e on e.deptno = d.deptno
3 order by d.dname, e.job, e.sal;
DNAME ENAME JOB SAL
-------------- ---------- --------- ----------
ACCOUNTING MILLER CLERK 1300
ACCOUNTING CLARK MANAGER 2450
ACCOUNTING KING PRESIDENT 5000
RESEARCH SCOTT ANALYST 3000
RESEARCH FORD ANALYST 3000
RESEARCH SMITH CLERK 800
RESEARCH ADAMS CLERK 1100
RESEARCH JONES MANAGER 2975
SALES JAMES CLERK 950
SALES BLAKE MANAGER 2850
SALES MARTIN SALESMAN 1250 --> for testing, I'll fetch SALESMEN
SALES WARD SALESMAN 1250 --> who earn 1250 USD (it's probably USD)
SALES TURNER SALESMAN 1500
SALES ALLEN SALESMAN 1600
14 rows selected.
Code that searches through it looks like this:
SQL> create or replace function search_sp
2 (par_dname in varchar2,
3 par_job in varchar2,
4 par_sal in number
5 )
6 return sys_refcursor
7 is
8 rc sys_refcursor;
9 begin
10 open rc for
11 select d.dname, e.ename, e.job, e.sal
12 from dept d join emp e on e.deptno = d.deptno
13 where (d.dname = par_dname or par_dname is null)
14 and (e.job = par_job or par_job is null)
15 and (e.sal = par_sal or par_sal is null);
16 return rc;
17 end;
18 /
Function created.
Let's test it:
SQL> select search_sp(null, 'SALESMAN', 1250) from dual;
SEARCH_SP(NULL,'SALE
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME ENAME JOB SAL
-------------- ---------- --------- ----------
SALES WARD SALESMAN 1250
SALES MARTIN SALESMAN 1250
SQL>
Looks OK to me.