Oracle function dynamic column - sql

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;

Related

Oracle Cursor within a Package not working - ORA 06512

I am trying to build a package that will take in a table of table names and either drop from or delete those tables. I am using dynamic sql, and dropping or deleting the tables works, but I need both the procedures to loop through all of the table names passed back to it.
I've tried mulitple ways - including trying to create a FOR Loop and a cursor. Here is a similar function I wrote in PostgreSQL that works but I'm having trouble translating it to Oracle.
Here is my function in PostgreSQL that works:
CREATE OR REPLACE FUNCTION drop_tables_for_stnd_mod_build(tablenames text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
tab_name RECORD;
BEGIN
FOR tab_name IN EXECUTE 'SELECT table_name FROM ' || tablenames
LOOP
EXECUTE 'DROP TABLE ' || tab_name.table_name || ' CASCADE';
END LOOP;
END;
$function$
;
And the procedure I'm writing as part of a package in Oracle
CREATE OR REPLACE PACKAGE BODY stnd_build_table_cleanup
AS
PROCEDURE drop_tables(table_in CLOB)
IS
TYPE cur_type is REF CURSOR;
c cur_type;
query_string VARCHAR(300);
loop_string VARCHAR(300);
table_name VARCHAR(100);
BEGIN
loop_string := 'SELECT tablenames FROM :table';
OPEN c FOR loop_string USING table_in;
LOOP
FETCH c INTO table_name;
query_string := 'DROP TABLE ' || table_name || ' CASCADE CONSTRAINTS';
-- dbms_output.PUT_LINE (query_string);
EXECUTE IMMEDIATE query_string;
EXIT WHEN c%NOTFOUND;
END LOOP ;
CLOSE c;
END drop_tables;
Here is the error I get when I try to call my function: Error report -
ORA-00903: invalid table name
ORA-06512: at "AMS_NYS.STND_BUILD_TABLE_CLEANUP", line 13
ORA-06512: at line 2
00903. 00000 - "invalid table name"
*Cause:
*Action:
Thanks!
Here's one possibility. Note that I coded this as a standalone procedure for simplicity.
CREATE OR REPLACE TYPE table_type IS TABLE OF VARCHAR2(128);
CREATE OR REPLACE PROCEDURE drop_tables(tables_to_drop_in table_type)
IS
BEGIN
FOR i IN tables_to_drop_in.FIRST .. tables_to_drop_in.LAST LOOP
--DBMS_OUTPUT.PUT_LINE(tables_to_drop_in(i));
EXECUTE IMMEDIATE 'DROP TABLE ' || tables_to_drop_in(i) || ' CASCADE CONSTRAINTS';
END LOOP;
END drop_tables;
DECLARE
tables_to_drop table_type;
BEGIN
tables_to_drop := table_type('TBL1','TBL2', 'TBL3');
drop_tables(tables_to_drop);
END;

How to convert to procedure

I need help in creating this anonymous block into a procedure.
I am new to PLSQL. Any help will be much appreciated. Thank you in advance.
I would like this query to run just by calling a procedure.
TRUNCATE TABLE dblink_status_tbl;
set serveroutput on;
-- or equivalent for your client
declare
-- l_dummy dual.dummy%type;
l_dummy VARCHAR2(20);
l_status VARCHAR2(100);
begin
for r in
(select db_link from all_db_links where db_link in
( 'WP1',
'6P1',
'OP3',
'LP1',
'ODS')
and owner = 'CAMPER')
loop
begin
execute immediate 'select 1 from dual#' || r.db_link into l_dummy;
l_status:= 'ACTIVE';
dbms_output.put_line(r.db_link|| ',' || l_status);
rollback work;
execute immediate 'alter session close database link ' || r.db_link;
exception
when others then
l_status:= sqlerrm;
l_status := replace(replace(l_status,CHR(13), ' '),CHR(10),' ');
l_status := '"' || l_status || '"';
dbms_output.put_line(r.db_link|| ',' || l_status);
end;
insert into dblink_status_tbl
values(r.db_link,l_status);
commit;
end loop;
end;
Basically, you need only the first line in my example:
create or replace procedure p_your_proc as
-- from now on, it is your original code
l_dummy VARCHAR2(20);
l_status VARCHAR2(100);
begin
...
end;
/
Once it is created, run it as
begin
p_your_proc;
end;
/
P.S.
At the beginning, you're truncating a table - if it is necessary within the procedure, you'd use dynamic SQL (as it is a DDL):
begin
execute immediate ('TRUNCATE TABLE dblink_status_tbl');
...
end;
Or, simply delete its contents as
begin
delete from dblink_status_tbl;
...
end;
Make it like something:
create or replace procedure proc_name as
l_dummy VARCHAR2(20);
l_status VARCHAR2(100);
begin
...
Using l_dummy,l_status
end;
And run this like :
"Exec proc_name" or "execute proc_name"

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|| '''';

can TYPE be declared of ref cursor rowtype

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.

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;