can TYPE be declared of ref cursor rowtype - sql

TYPE ref_cur IS REF CURSOR;
ref_cur_name ref_cur;
TYPE tmptbl IS TABLE OF ref_cur_name%ROWTYPE;
n_tmptbl tmptbl;
I tried this code but can't get it thru compiler . Is there a way to store the results of ref cursor into a table ?
NOTE-I need a table because i need to access the column of ref cursor . Using dbms_sql to access records of ref cursor is a bit tough for me .
UPDATE :
/* Formatted on 8/1/2013 4:09:08 PM (QP5 v5.115.810.9015) */
CREATE OR REPLACE PROCEDURE proc_deduplicate (p_tblname IN VARCHAR2,
p_cname IN VARCHAR2,
p_cvalue IN VARCHAR2)
IS
v_cnt NUMBER;
TYPE ref_cur IS REF CURSOR;
ref_cur_name ref_cur;
v_str1 VARCHAR2 (4000);
v_str2 VARCHAR2 (4000);
v_str3 VARCHAR2 (4000);
BEGIN
v_str1 :=
'SELECT ROWID v_rowid FROM '
|| p_tblname
|| ' WHERE '
|| p_cname
|| '='''
|| p_cvalue
|| '''';
BEGIN
v_str2 :=
'SELECT COUNT ( * )
FROM '
|| p_tblname
|| ' WHERE '
|| p_cname
|| ' = '''
|| p_cvalue
|| '''';
logerrors ('proc_deduplicate',
'count exception',
SQLCODE,
v_str2 || SQLERRM,
'e');
EXECUTE IMMEDIATE v_str2 INTO v_cnt;
EXCEPTION
WHEN OTHERS
THEN
logerrors ('proc_deduplicate',
'count exception',
SQLCODE,
SQLERRM,
'e');
END;
IF v_cnt IS NOT NULL
THEN
OPEN ref_cur_name FOR v_str1;
LOOP
IF v_cnt = 1
THEN
EXIT;
ELSE
BEGIN
v_str3 :=
'DELETE FROM '
|| p_tblname
|| ' WHERE ROWID = v_rowid ';
-- THIS IS THE PROBLEM . i just created an alias above for rowid keyword but i guess, DBMS sql will have to be used after all .
EXECUTE IMMEDIATE v_str3;
EXCEPTION
WHEN OTHERS
THEN
logerrors (
' proc_deduplicate
',
' delete exception
',
SQLCODE,
SQLERRM,
' e
'
);
END;
END IF;
v_cnt := v_cnt - 1;
END LOOP;
END IF;
EXCEPTION
WHEN OTHERS
THEN
logerrors (
' proc_deduplicate',
' final exception
',
SQLCODE,
SQLERRM,
' e'
);
END;
/

By issuing TYPE ref_cur IS REF CURSOR you are declaring a weak cursor. Weak cursors return no specified types. It means that you cannot declare a variable that is of weak_cursor%rowtype, simply because a weak cursor does not return any type.
declare
type t_rf is ref cursor;
l_rf t_rf;
type t_trf is table of l_rf%rowtype;
l_trf t_trf;
begin
null;
end;
ORA-06550: line 4, column 27:
PLS-00320: the declaration of the type of this expression is incomplete or malformed
ORA-06550: line 4, column 3:
PL/SQL: Item ignored
If you specify return type for your ref cursor, making it strong, your PL/SQL block will compile successfully:
SQL> declare -- strong cursor
2 type t_rf is ref cursor return [table_name%rowtype][structure];
3 l_rf t_rf;
4 type t_trf is table of l_rf%rowtype;
5 l_trf t_trf;
6 begin
7 null;
8 end;
9 /
PL/SQL procedure successfully completed

As far as I understand what you're doing, you just need to parameterise the delete:
...
v_str3 VARCHAR2 (4000);
v_rowid ROWID;
BEGIN
...
OPEN ref_cur_name FOR v_str1;
LOOP
FETCH ref_cur_name INTO v_rowid;
EXIT WHEN ref_cur_name%NOTFOUND;
IF v_cnt = 1
THEN
EXIT;
ELSE
BEGIN
v_str3 :=
'DELETE FROM '
|| p_tblname
|| ' WHERE ROWID = :v_rowid ';
EXECUTE IMMEDIATE v_str3 USING v_rowid;
...
You need to fetch the ref_cur_name into a variable, which needs to be declared obviously, and then use that as a bind variable value in the delete.
You should do the same thing with the p_cvalue references in the other dynamic SQL too. You could probably make this much simpler, with a single delete and no explicit count, in a single dynamic statement:
CREATE OR REPLACE PROCEDURE proc_deduplicate (p_tblname IN VARCHAR2,
p_cname IN VARCHAR2,
p_cvalue IN VARCHAR2)
IS
BEGIN
execute immediate 'delete from ' || p_tblname
|| ' where ' || p_cname || ' = :cvalue'
|| ' and rowid != (select min(rowid) from ' || p_tblname
|| ' where ' || p_cname || ' = :cvalue)'
using p_cvalue, p_cvalue;
END proc_deduplicate;
/
SQL Fiddle.
If you wanted to know or report how many rows were deleted, you could refer to SQL%ROWCOUNT after the execute immediate.

Strong ref cursor returns defined values, but weak is free to anything, totally dynamic.
But we can't define rowtype variable on weak ref cur
e.g.
declare
refcur sys_refcursor;
emprec refcur%rowtype; --it'll yield error
while
declare
type empref is ref cursor returns employees%rowtype;
empcur empref;
emprec empcur%rowtype; --it'll work fine.
This is very useful, now we can define collection on them and many other advantage if you talk about practically.

No. You're trying to declare the type against an instance of the cursor anyway, so you'd be closer with:
TYPE tmptbl IS TABLE OF ref_cur%ROWTYPE;
but you still can't do that, you'd get PLS-00310: with %ROWTYPE attribute, 'REF_CUR' must name a table, cursor or cursor-variable.
A ref cursor is weakly-typed, so the compiler doesn't know what a record would look like. You could open the ref cursor for different results depending on the logic in the block, or a dynamic query, and the compiler would have no way to know what to expect in advance.
The PL/SQL documentation state that %rowtype can apply to an explicit cursor, a strong cursor variable, or a table or view. And this compares strong and weak cursor variables.
If you know what your query will be you can declare a record type with those fields, or against a table %rowtype if you're be querying a single table. Since you're using dbms_sql I guess you won't know that though. Maybe if you updated your question with more information about what you're actually trying to do there would be some other approaches you could try.

Related

Passing table name and column name dynamically to PL/SQL Stored procedure

I am trying to pass table name and column name to a stored procedure in oracle , but it gives me following error: table or view does not exist
Below is the code:
create or replace procedure jz_dynamic_sql_statement
(p_table_name in varchar2,
p_col1_name in varchar2,
p_check_result out integer)
as
v_error_cd est_runtime_error_log.error_cd%type;
v_error_msg est_runtime_error_log.error_msg%type;
v_sql varchar2(1024);
v_result number(10);
begin
v_result := 0;
v_sql := 'select count(*) from ' || p_table_name ||' WHERE COLUMNNAME=' || p_col1_name;
execute immediate v_sql into v_result;
p_check_result := v_result;
end;
If the error coming back says the table does not exist then that means the table you pass in does not exist or the user that the procedure runs under cannot access it.
You could add a dbms_output.put_line statement to display the query that you are building and then try running it yourself, before you attempt the execute immediate. Then you know what errors you need to fix.
dbms_output.put_line('query : '||v_sql);
Be sure to turn on dbms_output.
Also, from what it looks like you are trying to do, you will need to pass the column name AND column value. Unless the tables you are querying will ALWAYS have the column name "COLUMNNAME".
Try this:
v_sql := 'select count(*) from ' || p_table_name ||' WHERE COLUMNNAME=''' || p_col1_name|| '''';

plsql: execute query: invalid identifier

Why can't I execute the stmt query?
It shows tabb is invalid identifier.
TYPE tab_row IS TABLE OF tab_name%ROWTYPE;
tabb tab_row;
TYPE cur_ref Is Ref Cursor;
c cur_ref;
stmt_string Varchar2(1000);
stmt Varchar2(1000);
Begin
stmt_string := 'Select * from ' || tab1 || ' mft
Where mft.mfmt_id IS NOT NULL
and mft.MFMT_FLAG IS NULL';
Open c For stmt_string;
Fetch c bulk collect into tabb;
Close c;
stmt:= 'Update '|| tab || ' m
Set m.mfmt_mat_bnr = tabb(i).mfmt_mat_bnr,
m.mfmt_mat_type = tabb(i).mfmt_mat_type,
m.mfmt_be_seg = tabb(i).mfmt_be_seg
Where m.mfmt_id = tabb(i).mfmt_id';
For i in 1..tabb.count loop
Execute Immediate stmt;
End loop;
End;
I have not seen where is tabb being declared. As you mentioned in your comment, it is a collection.
From your code I can understand you are looping through the values of this collection. This means these values are variables i.e. changing values every iteration. Therefore, you need to use them as such.
You current dynamic statement, this values are static string. This Set m.mfmt_mat_bnr = tabb(i).mfmt_mat_bnr, will result in the same string every iteration. Hence, you need to change that to Set m.mfmt_mat_bnr = '|| tabb(i).mfmt_mat_bnr||', ...etc
I can rewrite your code as follows:
TYPE tab_row IS TABLE OF tab_name%ROWTYPE;
tabb tab_row;
TYPE cur_ref Is Ref Cursor;
c cur_ref;
stmt_string Varchar2(1000);
stmt Varchar2(1000);
Begin
stmt_string := 'Select * from ' || tab1 || ' mft
Where mft.mfmt_id IS NOT NULL
and mft.MFMT_FLAG IS NULL';
Open c For stmt_string;
Fetch c bulk collect into tabb;
Close c;
stmt:= 'Update '|| tab || ' m
Set m.mfmt_mat_bnr = '||tabb(i).mfmt_mat_bnr||',
m.mfmt_mat_type = '||tabb(i).mfmt_mat_type||',
m.mfmt_be_seg = '||tabb(i).mfmt_be_seg||'
Where m.mfmt_id = '||tabb(i).mfmt_id;
For i in 1..tabb.count loop
Execute Immediate stmt;
End loop;
End;
Since this code is not tested, one thing I strongly recommend to confirm first. Is that tabb is correctly declared within the scope.
If the problem is in your loop where you execute your statement, maybe try using EXECUTE IMMEDIATE .. USING statement?
The syntax is:
EXECUTE IMMEDIATE <SQL Command>
[INTO <variable list>]
[USING <bind variable list>];
You might try the code below, then:
DECLARE
-- your tab1 may contain some insignificant columns with values (especially huge ones e.g. CLOB/BLOB)
-- which will extremely affect procedure's efficiency, so it's a better practice to select only those values that we need
TYPE t_tab_rec IS RECORD (
mfmt_mat_bnr tab_name.mfmt_mat_bnr%TYPE
,mfmt_mat_type tab_name.mfmt_mat_type%TYPE
,mfmt_be_seg tab_name.mfmt_be_seg%TYPE
,mfmt_id tab_name.mfmt_id%TYPE
);
TYPE t_tab_arr IS TABLE OF t_tab_rec;
tabb t_tab_arr := NEW t_tab_arr();
TYPE ref_cur IS REF CURSOR RETURN t_tab_rec; -- to assure cursor is returning t_tab_rec type records
c cur_ref;
stmt_select VARCHAR2(1000);
stmt_upadte VARCHAR2(1000);
BEGIN
stmt_select :=
'SELECT
mft.mfmt_mat_bnr
,mft.mfmt_mat_type
,mft.mfmt_be_seg
,mft.mfmt_id
FROM
'||tab1||' mft
WHERE
mft.mfmt_id IS NOT NULL
AND mft.mfmt_flag IS NOT NULL';
OPEN c FOR stmt_select;
FETCH c BULK COLLECT INTO tabb;
CLOSE c;
stmt_upadte :=
'UPDATE '||tab||'
SET mfmt_mat_bnr = :p_mfmt_mat_bnr
,mfmt_mat_type = :p_mfmt_mat_type
,mfmt_be_seg = :p_mfmt_be_seg
WHERE
mfmt_id = :p_mfmt_id';
FOR idx IN tabb.FIRST .. tabb.LAST
LOOP
EXECUTE IMMEDIATE stmt_update USING tabb(idx).mfmt_mat_bnr, tabb(idx).mfmt_mat_type, tabb(idx).mfmt_be_seg, tabb(idx).mfmt_id;
END LOOP;
END;
/

SQL Select Like Column Name from table

The goal is to create a Oracle Function that is capable of query column name off a token provided by the user as to create a function with such capabilities
select cols_like('%e%') from table
This is the point I am currently at
CREATE OR REPLACE Function COLS_LIKE
(v_search in VARCHAR2, v_table in VARCHAR2)
RETURN VARCHAR
IS
TYPE r_cursor IS REF CURSOR;
c_emp r_cursor;
crs_cols VARCHAR(255);
column_list VARCHAR(1000);
BEGIN
OPEN c_emp FOR
'select COLUMN_NAME from cols
where TABLE_NAME = ''' || v_table || '''
and column_name like ''' || v_search || '''';
LOOP
FETCH c_emp INTO crs_cols;
EXIT WHEN c_emp%NOTFOUND;
if column_list IS NULL THEN
column_list := crs_cols;
else
column_list := column_list || ', ' || crs_cols;
end if;
END LOOP;
RETURN column_list;
END;
Where you call the function such as this
Declare
tests VARCHAR(100);
sql_stmt VARCHAR2(200);
begin
tests := COLS_LIKE('%E%', 'table');
DBMS_OUTPUT.PUT_LINE(tests);
-- OR
sql_stmt := 'select ' || COLS_LIKE('%E%', 'table') || ' from table';
DBMS_OUTPUT.PUT_LINE(sql_stmt);
end;
The end goal would be something such as this
select COLS_LIKE('%E%', 'table') from table;
What modifications can I make to my function or how I am calling to so that this function can be applied correctly.
Why you'd want to do such a thing I've no idea but you could return an open cursor to PL/SQL fairly easily:
create or replace function cols_like (
PTable in varchar2
, PColumn in varchar2
) return sys_refcursor
l_cols varchar2(32767);
c_curs sys_refcursor;
begin
select listagg(column_name, ', ') within group (order by column_id)
into l_cols
from user_tab_cols
where table_name = upper(Ptable)
and column_name like '%' || upper(PColumn) || '%'
;
open c_curs for '
select ' || l_cols || '
from ' || Ptable;
return c_curs;
end;
/
Returning this to a standard SQL statement will be a lot more difficult, this is because in selecting this function you're only selecting one column's worth of data. You want to be able to select N columns, which means you need to start returning nested tables that have been dynamically created.
I'm sure it's possible; but, before you get anywhere close to starting to attempt to do this think about why you're doing it. Ask a question where you don't state your end goal but where you state what your actual problem is. Chances are there's a lot simpler solution.
I was also having same problem and found this, and it is working for me. I was just making comparison of mobile numbers from two tables and in one of the tables some numbers have 0 in start and some don't. Finally got solution and just added % in start:
a.number was having numbers, in some of them starting 0 was missing. and b.number was accurate.
b.number like CONCAT('%',a.number)

Oracle function dynamic column

I have a function, and I want to determine the name of the column in run time. For this I am passing one variable as an argument, like column_name.
Below is the code with the function:
l_column_name as varchar2(100)
Begin
If(column_name='emp_name')
Then
l_column_name:=EMPLOYEE.EMP_NAME
End If;
begin
select l_column_name from employee
end;
In above code, l_column_name:=EMPLOYEE.EMP_NAME is giving the error
Not allowed in this context.
Any help is much appreicated.
Regards,
Chaitu
As the error says, you can not do this.
You need to look into using the PL/SQL Execute Immediate: http://docs.oracle.com/cd/B14117_01/appdev.101/b10807/13_elems017.htm
declare
l_column_name as varchar2(100);
l_column_results as VARCHAR2(100);
begin
if (column_name = 'emp_name') then
l_column_name := 'EMPLOYEE.EMP_NAME';
end if;
query := 'SELECT ' || l_column_name || ' from employeee';
EXECUTE IMMEDIATE query INTO l_column_results;
end;

writing a generic procedure in oracle

i want to write procedure which accents name of 2 tables as arguments and then compare the number or rows of the 2.
Also i want to each field of the 2 columns.The row which has a missmatch shold be
moved to another error table.
Can anyone give a PL/SQL procedure for doing this.
I want to achive this in oracle 9
Pablos example wont work, the idea is right though.
Something like this do it.
create or replace PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_r1 number;
v_r2 number;
v_sql1 varchar2(200);
v_sql2 varchar2(200);
BEGIN
v_sql1 := 'select count(1) from ' || T1;
v_sql2 := 'select count(1) from ' || T2;
EXECUTE IMMEDIATE v_sql1 into v_r1;
EXECUTE IMMEDIATE v_sql2 into v_r2;
dbms_output.put_line(T1 || ' count = ' || v_r1 || ', ' || T2 || ' count = ' || v_r2);
END;
DBMS_SQL is your friend for such operations.
You can use dynamic sql in PL/SQL. EXECUTE IMMEDIATE is your friend.
So, if you take two table names and trying to compare their row counts, you would do something like:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2(200), T2 IN VARCHAR2(200)) AS
v_cursor integer;
v_r1 integer;
v_r2 integer;
v_sql varchar2(200);
BEGIN
v_sql := "select count(1) into :1 from " || T1;
EXECUTE IMMEDIATE v_sql USING v_r1;
v_sql := "select count(1) into :1 from " || T2;
EXECUTE IMMEDIATE v_sql USING v_r2;
-- compare v_r1 and v_r2
END;
Not 100% sure about PL/SQL syntax. It's been a while since the last time I coded in great PL/SQL!
You can achieve same results with similar approach using DBMS_SQL. Syntax is a little bit more complicated though.
I am just posting here to note that all answers gravitate around dynamic SQL, and do not turn the attention to the implied problems using it.
Consider passing the following string as first or second parameter:
dual where rownum = 0 intersect
SELECT 0 FROM dual WHERE exists (select 1 from user_sys_privs where UPPER(privilege) = 'DROP USER')
I'll leave it to that.
To answer your question - Oracle actually stores these values in the data dictionary, so if you have access to it:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_text varchar2(1000);
BEGIN
select listagg(owner || ' ' || table_name || ' count = ' || num_rows, ',')
into v_text
from all_tables --user, all or dba tables depends on requirements
where table_name in (T1, T2);
dbms_output.put_line(v_text);
exception
when others then raise; -- Put anything here, as long as you have an exception block
END COMPARE_ROW_COUNT;