im trying to do something like this:
create function getData(r1 IN TABLE1%ROWTYPE,col1 string, valor OUT string)RETURN string AS
instruccion VARCHAR2(500);
valor VARCHAR(200);
BEGIN
valor := r1.col1;
return valor;
END getData;
A procedure that takes as parameters a record and the name of the column and give back the value and:
CREATE procedure p1 as
vData VARCHAR2(80);
v1 VARCHAR2(80);
vValue VARCHAR2(80);
inst VARCHAR2(500);
CURSOR vTable2 IS SELECT * FROM TABLE2;
CURSOR vTable1 IS SELECT * FROM TABLE1;
BEGIN
--A cursor for the table with the data
FOR d1 IN vTable1
LOOP
--A cursor for the table that store the name of the columns of the table 1
FOR v1 IN vTable2
LOOP
--get the table1 column name
vData := v1.table2Col1;
--calls the procedure that gives back the value of that record on the column name is stored in VData
inst := 'begin getData(:d1, :vData, :vValue ); end;';
EXECUTE IMMEDIATE inst USING in d1 , vData, OUT vValue;
DBMS_OUTPUT.PUT_LINE(vValue);
END LOOP;
END LOOP;
END p1;
So in summary i just want to have a table that store the names of the table1 in table2, because I want the procedure to work with any table with the name table1, this code gives me the error "expression have to be of SQL types" for the parameters of the dynamic block and i understand it but can figure out a new way to do it. I have tried a lot of things but I'm kind of new in oracle and I really would appreciated the help.
inst := 'begin getData(:d1, :vData, :vValue ); end;';
EXECUTE IMMEDIATE inst USING in d1 , vData, OUT vValue;
I don't see any reason you need to use dynamic SQL here. All you want is to to pass %ROWTYPE parameter and process it. %ROWTYPE is treated as a RECORD, which you would be able to use in the PL/SQL program.
For example,
SQL> CREATE OR REPLACE
2 FUNCTION func(
3 i_dept IN NUMBER,
4 i_emp_rec IN emp%rowtype )
5 RETURN VARCHAR2
6 IS
7 BEGIN
8 RETURN 'DEPTNO = ' || i_dept || ' ENAME = ' || i_emp_rec.ename;
9 END;
10 /
Function created.
SQL>
SQL> SET serveroutput ON
SQL> DECLARE
2 v_deptno dept.deptno%type;
3 cur sys_refcursor;
4 v_ecur sys_refcursor;
5 v_emp emp%rowtype;
6 BEGIN
7 OPEN cur FOR SELECT d.deptno,
8 CURSOR
9 ( SELECT e.* FROM emp e WHERE e.deptno = d.deptno
10 ) e FROM dept d;
11 LOOP
12 FETCH cur INTO v_deptno, v_ecur;
13 EXIT
14 WHEN cur%notfound;
15 LOOP
16 FETCH v_ecur INTO v_emp;
17 EXIT
18 WHEN v_ecur%notfound;
19 dbms_output.put_line(func(v_deptno,v_emp));
20 END LOOP;
21 CLOSE v_ecur;
22 END LOOP;
23 CLOSE cur;
24 END;
25 /
DEPTNO = 10 ENAME = CLARK
DEPTNO = 10 ENAME = KING
DEPTNO = 10 ENAME = MILLER
DEPTNO = 20 ENAME = SMITH
DEPTNO = 20 ENAME = JONES
DEPTNO = 20 ENAME = SCOTT
DEPTNO = 20 ENAME = ADAMS
DEPTNO = 20 ENAME = FORD
DEPTNO = 30 ENAME = ALLEN
DEPTNO = 30 ENAME = WARD
DEPTNO = 30 ENAME = MARTIN
DEPTNO = 30 ENAME = BLAKE
DEPTNO = 30 ENAME = TURNER
DEPTNO = 30 ENAME = JAMES
PL/SQL procedure successfully completed.
SQL>
Related
DECLARE
v_annual_salary NUMBER;
BEGIN
SELECT SAL * 12 INTO v_annual_salary
FROM EMP,DEPT
WHERE EMPNO = 7722;
DBMS_OUTPUT.PUT_LINE ('Annual Salary of 7722 is ' || TO_CHAR(v_annual_salary));
END;
/
Unless DEPT table contains a single row, this query will return TOO_MANY_ROWS. Why do you cross-join EMP and DEPT? Should have been just
SQL> declare
2 v_annual_salary number;
3 begin
4 select sal * 12
5 into v_annual_salary
6 from emp
7 where empno = 7934;
8
9 dbms_output.put_line('Annual salary of 7934 is ' || to_char(v_annual_salary));
10 end;
11 /
Annual salary of 7934 is 15600
PL/SQL procedure successfully completed.
SQL>
(My EMP table doesn't have employee whose EMPNO = 7722 so I used 7934).
I've two cursors both have almost similar code just a little difference in the group by condition, I want if the id is 1 or 2 e.t.c then it should open cur1 else cur2, basically one cursor at a time. Is there any possibility of achieving this?
if id in (1,2,3,4) then
cursor_value:= 'cur1';
else
cursor_value := 'cur2';
end if;
for i in cursor_value loop
end loop;
You can use an OPEN-FOR statement. Example:
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
emp_record employees%ROWTYPE;
v_stmt_str VARCHAR2(200);
v_e_job employees.job%TYPE;
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'SELECT * FROM employees WHERE job_id = :j';
-- Open cursor & specify bind argument in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING 'MANAGER';
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO emp_record;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
/
One option would be to include both cursor's SELECT statements into cursor FOR loop, along with a condition that chooses which one of them will be used. For example:
SQL> declare
2 id number := &par_id;
3 begin
4 for cur_r in (select * from emp
5 where deptno = 10
6 and id in (1,2,3,4)
7 union all
8 select * from emp
9 where deptno = 20
10 and id not in (1,2,3,4)
11 )
12 loop
13 dbms_output.put_Line(cur_r.deptno ||' '||cur_r.ename);
14 end loop;
15 end;
16 /
Enter value for par_id: 1 --> ID = 1, so use SELECT for DEPTNO = 10
old 2: id number := &par_id;
new 2: id number := 1;
10 CLARK
10 KING
10 MILLER
PL/SQL procedure successfully completed.
SQL> /
Enter value for par_id: 823 --> ID <> 1, so use SELECT for DEPTNO = 20
old 2: id number := &par_id;
new 2: id number := 823;
20 SMITH
20 JONES
20 SCOTT
20 ADAMS
20 FORD
PL/SQL procedure successfully completed.
SQL>
The simple way could be to use if else condition.
DECLARE
type cur REF CURSOR;
c cur;
BEGIN
IF id in (1,2,3,4) THEN
OPEN c FOR 'cursor query 1';
ELSE
OPEN c FOR 'cursor query 2';
END IF ;
END;
Cheers!!
I am new to PL/SQL and I am trying to search a specific data from schema. I want to use nested cursors and make the query dynamic. Please help me out with the approach.
Here's an example which uses a loop (though, not nested loops, as you want - I'm not sure why) and dynamic SQL (execute immediate).
It is based on Scott's schema; I'm looking for number of appearances of the employee name "KING" in all tables in current schema (querying USER_TABLES), within the ENAME column (querying USER_TAB_COLUMNS).
This is just to give you an idea; feel free to develop it further.
Some sample data:
SQL> set serveroutput on
SQL> select ename from emp order by ename;
ENAME
----------
ALLEN
BLAKE
CLARK
FORD
JAMES
JONES
KING
MARTIN
MILLER
SMITH
TURNER
WARD
12 rows selected.
SQL> select ename from bonus;
ENAME
----------
KING
Let's search for that KING person:
SQL> declare
2 l_str varchar2(500);
3 l_cnt number := 0;
4 begin
5 for cur_r in (select u.table_name, u.column_name
6 from user_tab_columns u, user_tables t
7 where u.table_name = t.table_name
8 and u.column_name = 'ENAME'
9 )
10 loop
11 l_str := 'SELECT COUNT(*) FROM ' || cur_r.table_name ||
12 ' WHERE ' || cur_r.column_name || ' like (''%KING%'')';
13
14 execute immediate (l_str) into l_cnt;
15
16 if l_cnt > 0 then
17 dbms_output.put_line(l_cnt ||' : ' || cur_r.table_name);
18 end if;
19 end loop;
20 end;
21 /
1 : EMP
1 : BONUS
PL/SQL procedure successfully completed.
SQL>
The result says that KING appears once in EMP and BONUS tables.
try this..
SET SERVEROUTPUT ON SIZE 100000
----Final OP----------------------
CREATE OR REPLACE PROCEDURE StringQuery(names VARCHAR2)
IS
match_count INTEGER;
v_data_type VARCHAR2(255) :='VARCHAR2';
v_search_string VARCHAR2(4000) :=names;
BEGIN
FOR t IN (SELECT table_name, column_name FROM user_tab_columns where data_type = v_data_type) LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM '||t.table_name||' WHERE '||t.column_name||' = :1'
INTO match_count
USING v_search_string;
IF match_count > 0 THEN
dbms_output.put_line( 'Table Name: '||t.table_name ||', Column Name: '||t.column_name||', Found This Many Times: '||match_count );
--EXECUTE IMMEDIATE 'SELECT * FROM '||t.table_name||'WHERE '||t.column_name||'='||v_search_string||' ';
END IF;
END LOOP;
END;
/
CALL StringQuery('&Enter_String_Value');
I have the following trigger for update action.
CREATE OR REPLACE TRIGGER oferta_trigger
BEFORE UPDATE ON oferty
FOR EACH ROW
DECLARE
id_oferta number (10);
id_komis number (10);
id_test_komis number (10);
suma decimal(10,2) :=0;
CURSOR c1 IS
SELECT cena_aktualna, id_test_komis
FROM oferty
WHERE status = 'A';
BEGIN
id_oferta := :NEW.idk;
dbms_output.put_line(id_oferta);
SELECT komis_id INTO id_komis FROM oferty WHERE idk = id_oferta;
FOR i in c1 LOOP
IF i.id_test_komis = id_komis THEN
suma := suma + i.cena_aktualna;
END IF;
END LOOP;
UPDATE komisy SET wartosc_samochodow = suma WHERE idk = id_komis;
END;
During update operation.
UPDATE oferty SET status ='Z' WHERE idk =1;
I get the following error:
SQL Error: ORA-04091: table OFERTY is mutating, trigger/function may
not see it.
How to solve the problem. I think this is problem of getting id.
Here's an example based on Scott's schema. I altered the DEPT table, adding a new column (SUM_SAL) which is supposed to contain sum of all salaries in that department.
First, the good, old mutating table way.
SQL> create or replace trigger trg_sumsal
2 after update on emp
3 for each row
4 declare
5 l_sum number;
6 begin
7 select sum(sal) into l_sum
8 from emp
9 where empno = :new.empno;
10
11 update dept set sum_sal = l_sum
12 where deptno = :new.deptno;
13 end;
14 /
Trigger created.
SQL> update emp set sal = 5000 where ename = 'KING';
update emp set sal = 5000 where ename = 'KING'
*
ERROR at line 1:
ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG_SUMSAL", line 4
ORA-04088: error during execution of trigger 'SCOTT.TRG_SUMSAL'
SQL>
As we already knew, that won't work.
Now, the compound trigger:
SQL> create or replace trigger trg_sumsal
2 for update or insert on emp
3 compound trigger
4
5 l_deptno emp.deptno%type;
6
7 after each row is
8 begin
9 l_deptno := :new.deptno;
10 end after each row;
11
12 after statement is
13 l_sum number;
14 begin
15 select sum(sal) into l_Sum
16 from emp
17 where deptno = l_deptno;
18
19 update dept set sum_sal = l_sum
20 where deptno = l_deptno;
21 end after statement;
22 end;
23 /
Trigger created.
SQL> update emp set sal = 10000 where ename = 'KING';
1 row updated.
SQL> select * From dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK 13750
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Nice; that works!
[EDIT, after reading William's comment]
If several departments are affected within a single UPDATE statement, the above code won't work properly. Slightly adjusted, it looks like this & fixes that issue:
SQL> create or replace trigger trg_sumsal
2 for update or insert on emp
3 compound trigger
4
5 type t_tab is table of number;
6 l_tab t_tab := t_tab();
7
8 after each row is
9 begin
10 l_tab.extend;
11 l_tab(l_tab.last) := :new.deptno;
12 end after each row;
13
14 after statement is
15 l_sum number;
16 begin
17 for i in l_tab.first .. l_tab.last loop
18 select sum(sal) into l_Sum
19 from emp
20 where deptno = l_tab(i);
21
22 update dept set sum_sal = l_sum
23 where deptno = l_tab(i);
24 end loop;
25 end after statement;
26 end;
27 /
Trigger created.
Testing:
SQL> select * from dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> update emp set sal = 10000 where ename in ('SMITH', 'KING');
2 rows updated.
SQL> select * from dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK 13750
20 RESEARCH DALLAS 15975
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
So I have the following stored procedure:
CREATE OR REPLACE PROCEDURE stored_p
(
ntype IN NUMBER ,
p_ResultSet OUT TYPES.cursorType
)
AS
BEGIN
OPEN p_ResultSet FOR
select * from table where ttype in ntype;
END stored_p
and, I can call it like this:
VARIABLE resultSet REFCURSOR
EXEC stored_p(80001, :resultSet);
PRINT :resultSet
but I want to be able to call it like this:
VARIABLE resultSet REFCURSOR
EXEC stored_p([80001,80002], :resultSet);
PRINT :resultSet
How should I modify my stored procedure accordingly? I am doing this so that I can display the results in a Crystal Report... (just in case that affects anything).. Thanks!!
The best option would be to pass a collection
SQL> create type empno_tbl
2 is
3 table of number;
4 /
Type created.
SQL> create or replace procedure stored_p
2 (
3 empnos in empno_tbl,
4 p_rc out sys_refcursor )
5 as
6 begin
7 open
8 p_rc for select * from emp where empno in (select * from table(empnos));
9 end;
10 /
Procedure created.
SQL> var rc refcursor;
SQL> ed
Wrote file afiedt.buf
1 create or replace procedure stored_p
2 (
3 empnos in empno_tbl,
4 p_rc out sys_refcursor )
5 as
6 begin
7 open
8 p_rc for select * from emp where empno in (select * from table(empnos));
9* end;
SQL> begin
2 stored_p( new empno_tbl(7902,7934), :rc );
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> print rc
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- --------- ---------- ----------
DEPTNO FAKE_COL FOO
---------- ---------- ----------
7902 FORD ANALYST 7566 03-DEC-81 3000
20 1
7934 MILLER CLERK 7782 23-JAN-82 1300
10 1
Unfortunately, Crystal Reports may not be able to pass a proper collection to a stored procedure. If that is the case, you'd have to pass in a comma-separated list of numbers. Your procedure would then have to parse that comma-separated string into a collection. You can use (or modify) Tom Kyte's in_list function for this
SQL> ed
Wrote file afiedt.buf
1 create or replace function in_list(
2 p_string in varchar2
3 )
4 return empno_tbl
5 as
6 l_string long default p_string || ',';
7 l_data empno_tbl := empno_tbl();
8 n number;
9 begin
10 loop
11 exit when l_string is null;
12 n := instr( l_string, ',' );
13 l_data.extend;
14 l_data(l_data.count) :=
15 ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
16 l_string := substr( l_string, n+1 );
17 end loop;
18 return l_data;
19* end;
SQL> /
Function created.
SQL> ed
Wrote file afiedt.buf
1 create or replace procedure stored_p
2 (
3 empnos in varchar2,
4 p_rc out sys_refcursor )
5 as
6 begin
7 open p_rc
8 for select *
9 from emp
10 where empno in (select *
11 from table(in_list(empnos)));
12* end;
SQL> /
Procedure created.
SQL> ed
Wrote file afiedt.buf
1 begin
2 stored_p( '7902,7934', :rc );
3* end;
SQL> /
PL/SQL procedure successfully completed.
SQL> print rc
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- --------- ---------- ----------
DEPTNO FAKE_COL FOO
---------- ---------- ----------
7902 FORD ANALYST 7566 03-DEC-81 3000
20 1
7934 MILLER CLERK 7782 23-JAN-82 1300
10 1
Newer versions might have different options. I work some with Oracle 9 and 10, and I will typically pass in a string of comma-separated values and dynamically build the SQL. There are some significant dangers with SQL injection to be aware of, though.
You need to create a type..
create or replace type NUMBER_ARRAY as table of number;
CREATE OR REPLACE PROCEDURE stored_p
(
ntype IN NUMBER_ARRAY ,
p_ResultSet OUT TYPES.cursorType
)
You can loop it using..
for i in 1 .. ntype.count
loop
dbms_output.put_line( ntype(i) );
end loop;
To test it,
DECLARE
ntypetest NUMBER_ARRAY := NUMBER_ARRAY ();
BEGIN
FOR i IN 1 .. 5
LOOP
ntypetest.EXTEND;
ntypetest (i) := i;
END LOOP;
stored_p(ntypetest,..)
There may be some variation in syntax.
Of course you can pass in comma separated values too but that will come in as a string. Your string should be something like 'val1','val2','val3'. You need to be careful when you have numbers as the whole string will look like in ('2343,3444,2222') which will be treated as one value instead of multiple numbers as in (2343,3444,2222)