For loop with Table name in Stored Procedures - oracle

I am working on Oracle stored procedures.
My requirement is below
IF variable1 := 'true"
THEN
tableName=abr
ELSE
tableName=mvr
END IF;
FOR i IN (select unique(row1) as sc from tableName t where t.row2 = 'name') LOOP
BEGIN
-- required Logic
END
END LOOP;
But here I am not able to pass the table name in tableName parameter. How to do it?

You'll need to use Execute Immediate - it's designed for operations that aren't known until run time.
For normal operations, Oracle must know the tables and columns at compile time. You can't do SELECT * FROM tableName because it has no idea what tableName is and therefore it can't be compiled correctly.
Instead, you can do EXECUTE IMMEDIATE 'SELECT * FROM ' || tableName;
You can select your results INTO a variable, loop the result set, or BULK COLLECT into a structure and then iterate that.
For a simple select into, you can do this:
EXECUTE IMMEDIATE 'SELECT COL1, COL2 FROM ' || tableName INTO V_COL1, V_COL2
V_COL1 & V_COL2 are just local variables, tableName is a string representing your table name, and COL2 and COL2 are columns in the table you're selecting from. You can use the likes of ALL_TAB_COLUMNS to get the structure of a table dynamically.
Here is an example from Oracle docs:
CREATE OR REPLACE PROCEDURE query_invoice(
month VARCHAR2,
year VARCHAR2) IS
TYPE cur_typ IS REF CURSOR;
c cur_typ;
query_str VARCHAR2(200);
inv_num NUMBER;
inv_cust VARCHAR2(20);
inv_amt NUMBER;
BEGIN
query_str := 'SELECT num, cust, amt FROM inv_' || month ||'_'|| year
|| ' WHERE invnum = :id';
OPEN c FOR query_str USING inv_num;
LOOP
FETCH c INTO inv_num, inv_cust, inv_amt;
EXIT WHEN c%NOTFOUND;
-- process row here
END LOOP;
CLOSE c;
END;
/
http://docs.oracle.com/cd/B12037_01/appdev.101/b10795/adfns_dy.htm

You are going to have to build a for loop for each table then use your logic to determine which loop you will execute.

Related

Oracle query logic to identify email addresses across the dwh schemas

I was performing an activity to identify eMail addresses based on certain pattern (#xyz.de). I initially tried checking the DBA_TAB_COLS [data dictionary] view but this just finds email column names and I manually need to check the big list of tables. Instead of doing that, is there is a more effective way to just fetch the the pattern value #xyz.de ?
Database - oracle 11g
Query used
SET SERVEROUTPUT ON 100000
DECLARE
lv_count number(10):=0;
l_str varchar2 (1000);
lv_col_name varchar2(255) :='EMAIL';
BEGIN
FOR V1 IN
(select distinct table_name
from dba_tab_columns
where column_name = lv_col_name
order by table_name)
LOOP
dbms_output.put_line(lv_col_name||' '||v1.table_name);
END LOOP;
END;
Please note that
I don't exactly know the table or column names.
The value #xyz.de can be in any schema and any table and any column. This has to be identified but in an effective way.
Any suggestions?
i have used the above block query to fetch the email column along with the table name , but how can i achieve by searching certain value #xyz.de using the dynamic sql ?
I don't know what you want to do with the values that you are trying to extract, so the below code just prints them. Refer to PL/SQL Dynamic SQL from the Oracle documentation.
declare
type EMAILS is ref cursor;
L_CURSOR EMAILS;
cursor EMAIL_COLS is
select OWNER
,TABLE_NAME
,COLUMN_NAME
from DBA_TAB_COLS
where COLUMN_NAME like '%EMAIL%'
and OWNER <> 'SYS';
L_SQL varchar2(200);
L_EMAIL varchar2(500);
begin
for REC in EMAIL_COLS
loop
L_SQL := 'select ' || REC.COLUMN_NAME || ' from ' || REC.OWNER || '.' || REC.TABLE_NAME || ' where ' || REC.COLUMN_NAME || ' like ''%xyz.de''';
open L_CURSOR for L_SQL;
loop
fetch L_CURSOR into L_EMAIL;
exit when L_CURSOR%notfound;
DBMS_OUTPUT.PUT_LINE(L_EMAIL);
end loop;
end loop;
end;

Loop trough a fixed list of values

I have a list of tables that needs to be renamed before each data migration.
I came up with the procedure below to check if the table exists and eventually rename it, appending its creation date:
DECLARE
v_cnt PLS_INTEGER;
v_date varchar2(50);
v_table varchar2(50);
v_table_short varchar2(50);
BEGIN
v_table:='MT_TABLE_1';
v_table_short:=SUBSTR(v_table,8);
SELECT COUNT(*)
INTO v_cnt
FROM dba_tables
WHERE owner = 'OWNER'
AND table_name = v_table;
IF(v_cnt > 0) THEN
select CREATED into v_date FROM DBA_OBJECTS WHERE OBJECT_TYPE = 'TABLE' AND OBJECT_NAME=v_table;
execute immediate 'rename "'||v_table||'" to "'||v_table_short||'_'||v_date||'"';
END IF;
v_table:='MY_TABLE_2';
v_table_short:=SUBSTR(v_table,8);
SELECT COUNT(*)
INTO v_cnt
FROM dba_tables
WHERE owner = 'OWNER'
AND table_name = v_table;
IF(v_cnt > 0) THEN
select CREATED into v_date FROM DBA_OBJECTS WHERE OBJECT_TYPE = 'TABLE' AND OBJECT_NAME=v_table;
execute immediate 'rename "'||v_table||'" to "'||v_table_short||'_'||v_date||'"';
END IF;
END;
/
Now I want to avoid defining the v_table variable manually and repeating the code for each of the objects that I need to check for: is there a way to insert them as a list in a table variable or so, and have the code loop for each of the values there?
Thank you.
Yes, I like #Srinika's idea. Something like that:
CREATE TABLE table_list (
sort_order NUMBER UNIQUE,
long_name VARCHAR2(128 BYTE) PRIMARY KEY,
short_name VARCHAR2(128 BYTE) UNIQUE
);
INSERT INTO table_list VALUES (1,'MY_TABLE_1','MYTAB1');
INSERT INTO table_list VALUES (2,'MY_TABLE_2','MYTAB2');
I've added a column for the short name instead of computing it on the fly, as two tables might have the same first 8 characters in the name. The short_name needs to be unique else two tables will end up with the same short name. The sort order is only to do the renaming in the same order every time.
CREATE OR REPLACE PROCEDURE rename_tables AS
v_date VARCHAR2(8);
v_short_name VARCHAR2(128);
v_stmt VARCHAR2(200);
BEGIN
v_date := TO_CHAR(SYSDATE, 'YYYYMMDD');
FOR r IN (SELECT * FROM table_list ORDER BY sort_order) LOOP
v_short_name := r.short_name || '_' || v_date;
v_stmt := 'rename '||r.long_name||' to '||v_short_name;
DBMS_OUTPUT.PUT_LINE(v_stmt);
EXECUTE IMMEDIATE v_stmt;
END LOOP;
END rename_tables;
/
Calling this procedure would produce the output:
rename MY_TABLE_1 to MYTAB1_20200330
rename MY_TABLE_2 to MYTAB2_20200330
You'll need to add checks if the long table exists, and whether a renamed table is already there. I'd log the statements to a log table, too.
Regarding ": I would use them only if you have mixed case table names in the database.

Loop through plsql code for a number of tables

A master table is to be updated daily with the input data from two sources in two tables. The plsql code for processing the two tables are practically identical except for the table names. We have to separately log possible errors about the data in the input tables therefore have to run the code once each for the two input tables.
The attempted solution is by putting the table names in a variable, and cycle through the code twice:
declare
input_table varchar2(20);
begin
for i in (select column_value as var from table(sys.ODCIvarchar2List('MIDGETS', 'GIANTS'))) loop
if i.var = 'MIDGETS' then
input_table := 'midget_table';
elsif i.var = 'GIANTS' then
input_table := 'giant_table';
end if;
for rec in (select col1, col2 from input_table) loop
<the processing code>
end loop;
end;
/
The problem is that plsql does not seem to be aware that input_table is a variable. It "thinks" that input_table is literally the name of the table, and returns the (ORA-00942: table or view does not exist) error.
Since this is dynamic code, the EXECUTE IMMEDIATE was then tried:
declare
input_table varchar2(20);
begin
for ... 'MIDGETS', 'GIANTS' ... loop
input_table := ...
...
end loop;
for rec in ( EXECUTE IMMEDIATE 'select col1, col2 from ' || input_table ) loop
<processing>
end loop;
end;
/
But EXECUTE IMMEDIATE is not allowed either in this context.
Is there a way at all? Or is making two copies of the .sql file, one for MIDGETS and one for GIANTS, the only way out?
You can use dynamic query as below
declare
type crs_type is ref cursor;
c crs_type;
v_query varchar2(2000);
input_table varchar2(20);
v_col1 midgets.col1%type; -- assuming identical data types for common named columns
v_col2 midgets.col2%type;
begin
for ... 'MIDGETS', 'GIANTS' ... loop
input_table := ...
...
end loop;
v_query := 'select col1, col2 from ' || input_table;
open c for v_query;
loop
fetch c into v_col1, v_col2;
exit when c%notfound;
<processing>
end loop;
close c;
end;

Find a Value in Table Using SQL Developer

I inherited a large database and nobody seems to know which table/column a particular data set is coming from. I've spent a lot of time going through table by table in Oracle's SQL Developer, but I can't find it. Is there a way in SQLDeveloper to search the entire table for a single value. Something like:
select table_name from all_tab_columns where column_value='desired value';
The db has around 1K+ tables each with lots of columns so manually combing through this isn't working.
You can use the following script to search for a value in all columns of your schema. The execution time for the script will depend on the number of tables in your schema and the number of rows in each of your table.
Replace 'abc' with the value which you intend to search. Also, right now the script will search all VARCHAR2 columns. You can also insert the table names and counts into a table instead of doing a DBMS_OUTPUT.PUT_LINE.
DECLARE
CURSOR cur_tables
IS
SELECT table_name,
column_name
FROM user_tab_columns
WHERE data_type = 'VARCHAR2';
v_sql VARCHAR2(4000);
v_value VARCHAR2(50);
v_count NUMBER;
BEGIN
v_value := 'abc';
FOR c_tables IN cur_tables LOOP
v_sql := 'SELECT count(1) FROM ' || c_tables.table_name || ' WHERE ' || c_tables.column_name || ' = :val' ;
EXECUTE IMMEDIATE v_sql INTO v_count USING v_value;
IF v_count > 0 THEN
DBMS_OUTPUT.PUT_LINE('Table Name ' || c_tables.table_name || ' Column Name ' || c_tables.column_name || ' Row Count ' || v_count);
END IF;
END LOOP;
END;

PLSQL dynamic query

I have a table A which has column A which holds table names as values.
All these tables have a common column C. I need maximum value of this column for each table.
I tried this using dynamic SQL but I'm getting errors. Please suggest.
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
Cursor cursor_a IS
SELECT a FROM A;
BEGIN
Open cursor_a;
LOOP
Fetch cursor_a INTO c_table2;
EXIT WHEN cursor_a%notfound;
query1 := 'SELECT max(object_ref) AS "c_obj" FROM c_table' ;
EXECUTE IMMEDIATE query1;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
Close cursor_a;
END;
Dynamic SQL can't see your PL/SQL variable: you need to pass it a string which can be executed in the scope of the SQL engine. So you need to concatenate the table name with the statement's boilerplate text:
query1 := 'SELECT max(c) FROM ' || variable_name;
You also need to return the result of the query into a variable.
Here is how it works (I've stripped out some of the unnecessary code from your example):
DECLARE
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
BEGIN
for lrec in ( select a as tab_name from A )
LOOP
EXECUTE IMMEDIATE 'SELECT max(object_ref) FROM ' || lrec.tab_name
into c_obj ;
dbms_output.put_line('Maximum value: '|| lrec.tab_name
|| '='|| c_obj);
END LOOP;
END;
There is some miss match in veriables that you had used i.e.
declared as "c_table" but accessing as "c_table2"
Each table common column name is "C" but accessing as "object_ref"
In dynamic query use INTO keyword to store the value to your varibale
Suggestions
Use concat() function to prepare the query dynamically i.e. something like:
SET #SQL := CONCAT('SELECT max(c) INTO ', c_obj, ' FROM ',c_table);
Steps of implementing dynamic query is:
SET #SQL = <your dynamic query>
PREPARE stmt FROM #SQL;
EXECUTE stmt;
Sample code:
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
CURSOR cursor_a IS
SELECT a FROM A;
BEGIN
OPEN cursor_a;
LOOP
FETCH cursor_a INTO c_table;
EXIT WHEN cursor_a%notfound;
SET #SQL := CONCAT('SELECT max(object_ref) AS c_obj INTO ', c_obj, ' FROM ',c_table);
PREPARE stmt FROM #SQL;
EXECUTE stmt;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
CLOSE cursor_a;
END;

Resources