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))
Related
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.
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>
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
Below is the code which has to execute the exception block when no data is found in the main block. However the exception block is not getting executed even when the query doesn't return any record. The query joins with another table and when the result matches with the query it will pick the 1st record else it has to assign null values to the fields and return the cursor to UI. The count below for the cursor is always 0 and the exception block isn't getting executed .
BEGIN
wf_common.write_debug(vprocname, ' 111=', ts);
OPEN cinfo FOR SELECT
name,
phone,
alt_phone,
email
FROM
abc
WHERE
id IN (
SELECT
id
FROM
xyz
WHERE
emp_no = vempno
)
AND name IS NOT NULL
AND ROWNUM = 1;
wf_common.write_debug(vprocname, 'COUNT' || cinfo%rowcount, ts);
EXCEPTION
WHEN no_data_found THEN
wf_common.write_debug(vprocname, '222=', ts);
OPEN cinfo FOR SELECT
'' name,
'' phone,
'' alt_phone,
'' email
FROM
dual;
wf_common.write_debug(vprocname, 'COUNT' || cinfo%rowcount, ts);
END;
Replace WHEN NO_DATA_FOUND with
WHEN OTHERS
This is a cursor. It doesn't raise no_data_found. How will you know whether it'll return something or not? You won't until you
open it
fetch from it
Then you'll know.
So - unfortunately - you'll first have to check, and then decide what to do. For example (based on Scott's sample schema):
SQL> create or replace function f_test (par_deptno in emp.deptno%type)
2 return sys_refcursor
3 is
4 type cinfot is record (empno emp.empno%type,
5 ename emp.ename%type,
6 job emp.job%type);
7 cinfor cinfot;
8 cinfo sys_refcursor;
9 begin
10 -- in order to test whether it returns something, open & fetch
11 open cinfo for 'select empno, ename, job
12 from emp
13 where deptno = :a' using par_deptno;
14 fetch cinfo into cinfor;
15
16 -- has anything been found?
17 if cinfo%found then
18 -- something has been found - process it
19 close cinfo;
20 open cinfo for 'select empno, ename, job
21 from emp
22 where deptno = :a' using par_deptno;
23
24 else
25 -- nothing has been found; open another cursor
26 close cinfo;
27 open cinfo for select deptno, dname, loc
28 from dept;
29 end if;
30
31 return cinfo;
32 end;
33 /
Function created.
Testing: department 10 exists in EMP table so - return its contents:
SQL> select f_test(10) From dual;
F_TEST(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB
---------- ---------- ---------
7782 CLARK MANAGER
7839 KING PRESIDENT
7934 MILLER CLERK
Department 50 doesn't exist so - return DEPT table's contents:
SQL> select f_test(50) From dual;
F_TEST(50)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
I am returning a ref cursor(P_CUR_SUMMARY) and then taking count of the same query to return a string (WARN# / ERROR). I want to eliminate the Count query.
This proc is using an existing framework so I cant return the string with the ref cursor.
I dont want to create a temp table and store data. This will remove the Count query but increase IO.
Any other option to eliminate the Count query? I just want to know if the ref cursor has ANY records. Not interested in actual count.
Procedure:
V_MAIN_TITLE and P_CUR_SUMMARY - OUT parameters
...
OPEN P_CUR_SUMMARY FOR
select * from emp...
...
select count(1) into V_CNT from emp...
...
IF V_CNT > 0 then
SELECT 'WARN#' INTO V_MAIN_TITLE FROM dual;
ELSE
SELECT 'ERROR' INTO V_MAIN_TITLE FROM dual;
END IF;
As you were told, you'll have to fetch from a cursor in order to find out what it contains (use %NOTFOUND or %FOUND cursor attributes for such a purpose). If there's something to be returned, re-open the cursor (otherwise you'd lose the first row, the one you used to check what's in there).
Here's an example:
SQL> create or replace procedure p_test
2 (par_deptno in number, par_main_title out varchar2, par_cur_summary out sys_refcursor)
3 is
4 r_emp emp%rowtype;
5 begin
6 open par_cur_summary for
7 select *
8 from emp
9 where deptno = par_deptno;
10
11 -- fetch in order to find out whether there's something returned by the cursor
12 fetch par_cur_summary into r_emp;
13 if par_cur_summary%notfound then
14 par_main_title := 'ERROR';
15 else
16 par_main_title := 'WARN#';
17
18 -- now, re-open the cursor; otherwise, you'd lose the first row, the one
19 -- that has been fetched for "testing" purpose
20 open par_cur_summary for
21 select *
22 from emp
23 where deptno = par_deptno;
24 end if;
25 end;
26 /
Procedure created.
Let's test it:
SQL> var l_cur_sum refcursor
SQL> var l_mai_tit varchar2(30)
SQL> exec p_test(37, :l_mai_tit, :l_cur_sum);
PL/SQL procedure successfully completed.
SQL> print l_mai_tit
L_MAI_TIT
---------------------------------------------------------------------------------------
ERROR
SQL> print l_cur_sum
ERROR:
ORA-01002: fetch out of sequence
no rows selected
SQL> exec p_test(10, :l_mai_tit, :l_cur_sum);
PL/SQL procedure successfully completed.
SQL> print l_mai_tit
L_MAI_TIT
---------------------------------------------------------------------------------------
WARN#
SQL> print l_cur_sum
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09.06.1981 2450 10
7839 KING PRESIDENT 17.11.1981 5000 10
7934 MILLER CLERK 7782 23.01.1982 1300 10
SQL>
Look what happens if you don't re-open the cursor (by commenting those lines out):
if par_cur_summary%notfound then
par_main_title := 'ERROR';
else
par_main_title := 'WARN#';
/*
open par_cur_summary for
select *
from emp
where deptno = par_deptno;
*/
end if;
SQL> exec p_test(10, :l_mai_tit, :l_cur_sum);
PL/SQL procedure successfully completed.
SQL> print l_mai_tit
L_MAI_TIT
----------------------------------------------------------------------------------------
WARN#
SQL> print l_cur_sum
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
7839 KING PRESIDENT 17.11.1981 5000 10
7934 MILLER CLERK 7782 23.01.1982 1300 10
SQL>
See? CLARK is lost.
The number of rows that will be returned is not known until the query is executed (i.e. when rows are fetched). When the cursor has merely been opened that is still in the future.
Perhaps it makes more sense for the client to handle the condition where no rows are returned and raise its own errors.