writing a generic procedure in oracle - sql

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;

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

Datawarehousing Automation using stored procedure

I am trying to code a procedure where i can validate all the records are moved from source to target provided there is no transformation logic involved in between for that particular column. My approach is to take the group by count of source column and target column and match their count. If the count is 0 then all the records are matched for that particular column to the target table.
Further a minus from the 2 data group by count will provide the missing data.
Can any one help me further on this.
Stored procedure :
create or replace
PROCEDURE MOVE_CHECK
(SCHEMA_SOURCE IN VARCHAR2,SCHEMA_TARGET IN VARCHAR2, TABLE_SOURCE IN VARCHAR2, TABLE_TARGET IN VARCHAR2, COLUMN_SOURCE IN VARCHAR2,
COLUMN_TARGET IN VARCHAR2)
AS
A varchar2 (30);
B varchar2 (30);
C varchar2 (30);
D varchar2 (30);
E varchar2 (30);
F varchar2 (30);
COUNT_SOURCE number(38);
TEMP_1 VARCHAR2(500);
TEMP_2 VARCHAR2(500);
TEMP_3 VARCHAR2(500);
TEMP_4 VARCHAR2(500);
COUNT_QUERY number(38);
BEGIN
A:=SCHEMA_SOURCE;
B:=SCHEMA_TARGET;
C:=TABLE_SOURCE;
D:=TABLE_TARGET;
E:=COLUMN_SOURCE;
F:=COLUMN_TARGET;
-- checking the count of the source records
TEMP_1 :='select count ( '|| E ||' ) from ' || C;
EXECUTE IMMEDIATE TEMP_1 INTO COUNT_SOURCE;
DBMS_OUTPUT.PUT_LINE ('source_count:'||Count_source);
TEMP_2 :='CREATE GLOBAL TEMPORARY TABLE SET_SOURCE AS (SELECT COUNT(1) AS COUNT_SOURCE, '|| E ||' from ' || C || ' GROUP BY ' || E||' )';
EXECUTE IMMEDIATE TEMP_2;
TEMP_3 :='CREATE GLOBAL TEMPORARY TABLE SET_TARGET AS (SELECT COUNT(1) AS COUNT_TARGET, ' || F||'FROM '||D||' GROUP BY ' ||D ||' )';
EXECUTE IMMEDIATE TEMP_3;
TEMP_4:= 'SELECT COUNT(1) FROM SET_SOURCE INTERSECT SET_TARGET ';
EXECUTE IMMEDIATE TEMP_4 INTO COUNT_QUERY;
DBMS_OUTPUT.PUT_LINE ('OUTPUT:'||COUNT_QUERY);
IF COUNT_QUERY <> 0
THEN DBMS_OUTPUT.PUT_LINE ('PLEASE CHECK');
ELSE DBMS_OUTPUT.PUT_LINE ('DONE- NO MISMATCH');
END IF;
END MOVE_CHECK;
I am unable to run execute Temp_2,Temp_3,Temp_4
Error:
ORA-00955: name is already used by an existing object
ORA-06512: at "YDSCST.MOVE_CHECK", line 35
ORA-06512: at line 16
source_count:7
Process exited.
It generally helps if you print out the dynamic SQL and try to run it manually; it can make syntax errors much easier to spot. In this case if you called the original procedure for the DUAL table you'd see:
CREATE GLOBAL TEMPORARY TABLE SET_SOURCE AS (SELECT COUNT(1), DUMMY )
from DUAL GROUP BY DUMMY )
You'd then get the same ORA-00923 if you ran that, and it suggests the problem might be before the from. And looking at the code it's clear that there was an extra closing parenthesis after the column name. But you spotted that yourself and have now moved on to the next error.
The next error after that should be ORA-00998: must name this expression with a column alias, because you haven't specified what the temporary table's columns should be called. You can do that in the select or in the create:
TEMP_2 :='CREATE GLOBAL TEMPORARY TABLE SET_SOURCE (ROW_COUNT, COLUMN_NAME) '
|| 'AS (SELECT COUNT(1), ' || E || ' from ' || C || ' GROUP BY ' || E || ')';
But you seem to have found and fixed that yourself too as you're now getting an ORA-06502, which is because your TEMP variables are smaller than your query string now needs to be. But you've also fixed that yourself now, making them 500 characters instead of 100.
Your TEMP_3 is missing a space before its from; the value of F is currently concatenated to that directly, making that an invalid statement. And the group by is using D (table name) instead of F (column name)
TEMP_3 :='CREATE GLOBAL TEMPORARY TABLE SET_TARGET (ROW_COUNT, COLUMN_NAME) '
|| 'AS (SELECT COUNT(1), ' || F || ' FROM ' || D || ' GROUP BY ' || F ||' )';
That would be more obvious if you were using the procedure argument names instead of meaningless (and pointless) local variables.
Creating global temporary tables, or any structure, within a procedure is generally not a good idea. The second time you call this they will already exist and it will error on that. If you really need them at all, you should create the temporary tables once outside the procedure as a separate task, and just populate them inside the procedure.

Executing Dynamic Native SQL with Oracle Table type gives invalid identifier error

We have created Oracle Table type. And we have created an array of it. This is done as we dont know how many values can come which may be too much for a IN clause in sql query.
--Code Snippet -----
create or replace
TYPE "INPUTCODE"
as object
(pc varchar2(100) )
create or replace
TYPE "INPUTCODEARR"
IS TABLE OF inputcode;
create or replace
PROCEDURE "TEST_PROC" (testCodes IN inputcodeArr, timeHorizon IN NUMBER, p_recordset OUT SYS_REFCURSOR)
AS
var_sqlStmt VARCHAR2(4096);
BEGIN
var_sqlStmt := 'select t.a,t.b, t.c';
var_sqlStmt := var_sqlStmt || 'from test t';
if testCodes is not null then
var_sqlStmt := var_sqlStmt || ', table(testCodes) tc';
var_sqlStmt := var_sqlStmt || 'where tc.pc = t.name';
end if;
dbms_output.put_line('Final SQL Statement::' || var_sqlStmt);
open p_recordset for var_sqlStmt;
END TEST_PROC;
All the above ones are compiles and when you run the TEST_PROC procedure with few testCode values it will fail with error Invalid identifier for testCodes.
In the procedure, final sql statement which is printing in my console is correct and when you run this as a static sql statement inside procedure it runs without any error. But inside the dynamic sql it fails.
I tried executing using DYNAMIC_SQL package, but it results in same error.
Also, i tried giving it as a bind variable for 'table(testCodes)'. That also failed.
Please suggest.
You are using dynamic SQL, so you must tell Oracle which word is an identifier and which word is a variable.
Consider the following statement running directly in SQLPlus:
select t.a,t.b, t.c from test t, table(testCodes) tc
It will fail because no object is named testCodes in your DB. You have to tell the SQL engine that testCodes is in fact a variable. You have to do this because you have chosen to use dynamic SQL whereas variable binding is automatic in static SQL.
In most cases, you can bind "object" variables in the same way as standard variables. In PL/SQL there are several ways to do this, for instance with cursors you would use USING:
SQL> DECLARE
2 l_cur SYS_REFCURSOR;
3 l_tab inputcodeArr := inputcodeArr(INPUTCODE('A'), INPUTCODE('B'));
4 l_obj varchar2(100);
5 BEGIN
6 OPEN l_cur FOR 'SELECT pc FROM TABLE(:my_variable)' -- notice the ":"
7 USING l_tab; -- binding by position
8 LOOP
9 FETCH l_cur
10 INTO l_obj;
11 EXIT WHEN l_cur%NOTFOUND;
12 dbms_output.put_line(l_obj);
13 END LOOP;
14 CLOSE l_cur;
15 END;
16 /
A
B
PL/SQL procedure successfully completed
In your case however I wouldn't bother with dynamic SQL since you can open a cursor conditionally:
CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes IN inputcodeArr,
timeHorizon IN NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
BEGIN
IF testCodes IS NOT NULL THEN
OPEN p_recordset FOR
SELECT t.a, t.b, t.c FROM test t, TABLE(testCodes) tc
WHERE tc.pc = t.NAME;
ELSE
OPEN p_recordset FOR
SELECT t.a, t.b, t.c FROM test t;
END IF;
END TEST_PROC;
My advice would be to stick with static SQL as long as possible, as it's a lot easier to make mistakes with dynamic SQL.
Update following comment:
If your number of input is not constant and you have to use dynamic SQL because there are many combinations of filters, you can use the following strategy:
CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes IN inputcodeArr,
timeHorizon IN NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
l_sql LONG := 'SELECT t.a, t.b, t.c FROM test t WHERE';
BEGIN
-- filter #1
IF testCodes IS NOT NULL THEN
l_sql := l_sql || ' t.name IN (SELECT pc FROM TABLE(:filter1))';
ELSE
l_sql := l_sql || ' :filter1 IS NULL';
END IF;
-- filter #2
IF timeHorizon IS NOT NULL THEN
l_sql := l_sql || ' AND t.horizon = :filter2';
ELSE
l_sql := l_sql || ' AND :filter2 IS NULL';
END IF;
-- open cursor
OPEN p_recordset FOR l_sql USING testCodes, timeHorizon;
END TEST_PROC;
/
I'm making sure that the final SQL will always have the same number of variables in the same order, however each condition where the filter is NULL will be a tautology (NULL IS NULL).

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.

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)