Loop trough a fixed list of values - oracle

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.

Related

Create backup table concatenated with sysdate before deleting the rows in oracle procedure

I have created a package and defined a procedure to delete specific rows retrieved by the cursor.
Before the rows were deleted from the table, I want to take a backup of those records every time the package is compiled and I need the backup table to be created as tablename concatenated with sysdate.
ex: if table name is emp, backup table should be created as emp_2020_10_16
Below is the sample code I have created:
PROCEDURE DELETE_REC(
P_retcode NUMBER,
P_errorbuff VARCHAR2,
P_unit_id NUMBER,
P_join_date VARCHAR2
)
IS
CURSOR cur1
IS
SELECT unit_ID,dept_ID,join_DATE
FROM EMP MMT
WHERE MMT.dep_TYPE_ID IN (44,35)
AND MMT.unit_id = P_unit_id
AND MMT.join_date < to_date(P_join_date,'RRRR/MM/DD HH24:MI:SS');
BEGIN
--begin
-- EXECUTE IMMEDIATE 'Create table EMP_' || to_char(sysdate,'yyyy_mm_dd') || ' as select * from EMP MMT WHERE MMT.dep_TYPE_ID IN (44,35)
AND MMT.unit_id = P_unit_id
AND MMT.join_date < to_date(P_join_date,'RRRR/MM/DD HH24:MI:SS');
--
-- end;
/*Here i would like to create backup table like above before executing the below delete statement but i am not sure about the correct standards that i should be using for above dynamic statement*/
FOR val IN cur1
LOOP
DELETE
FROM EMP MMT
WHERE MMT.dept_ID= val.dept_id;
How can I backup the table using above dynamic statement in best possible way? I am still learning PL&SQL.
Maybe sth like this would help:
create table employees as select * from hr.employees;
--drop table emp_2020_10_18;
--drop table employees;
----------------
declare
vTabName varchar2(50);
nDept_id number := 10;
nCnt number := 0;
vSQL varchar2(1000);
begin
vTabName := 'emp_'||to_char(sysdate, 'yyyy_mm_dd');
-- check if table exists
begin
execute immediate 'select count(*) from emp_tmp' into nCnt;
exception when others then
nCnt := -1;
end;
-- if not exists create one
if nCnt = -1 then
execute immediate 'create table '|| vTabName||' as select * from employees where 1=2' ;
end if;
execute immediate 'insert into '|| vTabName ||' select * from employees where department_id = :nDept_id' using nDept_id;
delete from employees where department_id = nDept_id;
exception when others then
dbms_output.put_line(sqlerrm);
end;
/

Put Column Name in Variable and use it in output statement

Here is What i actually wanted to do, Fetch Data From a Table without knowing any columns but i.j.Column_Name gives an error, so i wanted to put it in a variable. After reading the comments i think it's not possible
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
CURSOR C2 IS SELECT Table_Name,Column_Name FROM user_tab_columns
WHERE data_type='VARCHAR2';
v_table Varchar2(256);
v_Col varchar2(200);
BEGIN
FOR i in C1 LOOP
FOR j in (SELECT Column_Name FROM user_tab_columns WHERE
Table_Name='Table_Name') LOOP
dbms_output.put_line(i.j.Column_Name);
END LOOP;
END LOOP;
END;
/
No, There is no Column Named v_Col
You can't refer to a field in a record (which is what the cursor loop is giving you) dynamically. If you need to do flexibly then you can use dbms_sql (possibly adapting this approach), but in the scenario you've shown you could use dynamic SQl to only get the column you want in the cursor:
-- dummy data
create table table_name (id number, column_name varchar2(10), other_col date);
insert into table_name values (1, 'Test 1', sysdate);
insert into table_name values (2, 'Test 2', sysdate);
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
v_Cur sys_refcursor;
v_Col varchar2(200);
v_Val varchar2(4000);
BEGIN
v_Col:= 'Column_Name';
OPEN v_Cur for 'SELECT ' || v_Col || ' FROM Table_Name';
LOOP
FETCH v_Cur INTO v_Val;
EXIT WHEN v_Cur%notfound;
dbms_output.put_line(v_val);
END LOOP;
END;
/
Test 1
Test 2
PL/SQL procedure successfully completed.
The downside of this is that whatever the data type of the target column is, you have to implicitly convert it to a string; but you would be doing that in the dbms_output call anyway. So if you change the column you want to print:
v_Col:= 'Other_Col';
then the output from my dummy data would be:
2018-08-23
2018-08-23
PL/SQL procedure successfully completed.
where the date value is being implicitly formatted as a string using my current NLS session settings.
You could get more advanced by checking the data type in user_tab_columns and changing the dynamic query and/or the fetch and handling, but it isn't clear what you really need to do.

ORACLE PL/SQL for each passing tablename to procedure

I need to do a FOR EACH loop in a procedure, but I need to pass the table name dynamically.
This is the declaration
CREATE OR REPLACE PROCEDURE MIGRATE_PRIMITIVES_PROPS
(
FromTable IN VARCHAR2,
ToTable IN VARCHAR2
)
When I try and do this
FOR EachRow IN (SELECT * FROM FromTable) It says the table isn't valid
The table coming into the procedure is dynamic, columns are added and deleted all the time so I can't spell out the columns and use a cursor to populate them.
You have to use dynamic SQL to query a table whose name you don't know at compile time. You can do that with a dynamic cursor:
as
l_cursor sys_refcursor;
begin
open l_cursor for 'select * from ' || fromtable;
loop
fetch l_cursor into ...
... but then it breaks down because you can't define a record type to fetch into based on a weak ref cursor; and you don't know the column names or types you're actually interested in - you're using select * and have specific names to exclude, not include. You mentioned an inner loop that works and gets the column names, but there is no way to refer to a field in that cursor variable dynamically either.
So you have to work a bit harder and use the dbms_sql package instead of native dynamic SQL.
Here's a basic version:
create or replace procedure migrate_primitives_props
(
fromtable in varchar2,
totable in varchar2
)
as
l_cursor pls_integer;
l_desc_tab dbms_sql.desc_tab;
l_columns pls_integer;
l_value varchar2(4000);
l_status pls_integer;
begin
l_cursor := dbms_sql.open_cursor;
-- parse the query using the parameter table name
dbms_sql.parse(l_cursor, 'select * from ' || fromtable, dbms_sql.native);
dbms_sql.describe_columns(l_cursor, l_columns, l_desc_tab);
-- define all of the columns
for i in 1..l_columns loop
dbms_sql.define_column(l_cursor, i, l_value, 4000);
end loop;
-- execute the cursor query
l_status := dbms_sql.execute(l_cursor);
-- loop over the rows in the result set
while (dbms_sql.fetch_rows(l_cursor) > 0) loop
-- loop over the columns in each row
for i in 1..l_columns loop
-- skip the columns you aren't interested in
if l_desc_tab(i).col_name in ('COL_NAME', 'LIB_NAME', 'PARTNAME',
'PRIMITIVE', 'PART_ROW')
then
continue;
end if;
-- get the column value for this row
dbms_sql.column_value(l_cursor, i, l_value);
-- insert the key-value pair for this row
execute immediate 'insert into ' || totable
|| '(key, value) values (:key, :value)'
using l_desc_tab(i).col_name, l_value;
end loop;
end loop;
end;
/
I've assumed you know the column names in your ToTable but still used a dynamic insert statement since that table name is unknown. (Which seems strange, but...)
Creating and populating sample tables, and then calling the procedure with their names:
create table source_table (col_name varchar2(30), lib_name varchar2(30),
partname varchar2(30), primitive number, part_row number,
col1 varchar2(10), col2 number, col3 date);
create table target_table (key varchar2(30), value varchar2(30));
insert into source_table (col_name, lib_name, partname, primitive, part_row,
col1, col2, col3)
values ('A', 'B', 'C', 0, 1, 'Test', 42, sysdate);
exec migrate_primitives_props('source_table', 'target_table');
End up with the target table containing:
select * from target_table;
KEY VALUE
------------------------------ ------------------------------
COL1 Test
COL2 42
COL3 2015-05-22 15:29:31
It's basic because it isn't sanitising the inputs (look up the dbms_assert package), and isn't doing any special handling for different data types. In my example my source table had a date column; the target table gets a string representation of that date value based on the calling session's NLS_DATE_FORMAT setting, which isn't ideal. There's a simple but slightly hacky way to get a consistent date format, and a better but more complicated way; but you may not have date values so this might be good enough as it is.

For loop with Table name in Stored Procedures

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.

Check table exist or not before create it in Oracle

Trying to check is table exist before create in Oracle. Search for most of the post from Stackoverflow and others too. Find some query but it didn't work for me.
IF((SELECT count(*) FROM dba_tables where table_name = 'EMPLOYEE') <= 0)
THEN
create table EMPLOYEE
(
ID NUMBER(3),
NAME VARCHAR2(30) NOT NULL
)
END IF;
Which gives me error
Error: ORA-00900: invalid SQL statement
SQLState: 42000
ErrorCode: 900
Position: 1
I search for the syntax for IF condition, I think which is also write.
Please suggest me....
As Rene also commented, it's quite uncommon to check first and then create the table.
If you want to have a running code according to your method, this will be:
declare
nCount NUMBER;
v_sql LONG;
begin
SELECT count(*) into nCount FROM dba_tables where table_name = 'EMPLOYEE';
IF(nCount <= 0)
THEN
v_sql:='
create table EMPLOYEE
(
ID NUMBER(3),
NAME VARCHAR2(30) NOT NULL
)';
execute immediate v_sql;
END IF;
end;
But I'd rather go catch on the Exception, saves you some unnecessary lines of code:
declare
v_sql LONG;
begin
v_sql:='create table EMPLOYEE
(
ID NUMBER(3),
NAME VARCHAR2(30) NOT NULL
)';
execute immediate v_sql;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -955 THEN
NULL; -- suppresses ORA-00955 exception
ELSE
RAISE;
END IF;
END;
/
I know this topic is a bit old, but I think I did something that may be useful for someone, so I'm posting it.
I compiled suggestions from this thread's answers into a procedure:
CREATE OR REPLACE PROCEDURE create_table_if_doesnt_exist(
p_table_name VARCHAR2,
create_table_query VARCHAR2
) AUTHID CURRENT_USER IS
n NUMBER;
BEGIN
SELECT COUNT(*) INTO n FROM user_tables WHERE table_name = UPPER(p_table_name);
IF (n = 0) THEN
EXECUTE IMMEDIATE create_table_query;
END IF;
END;
You can then use it in a following way:
call create_table_if_doesnt_exist('my_table', 'CREATE TABLE my_table (
id NUMBER(19) NOT NULL PRIMARY KEY,
text VARCHAR2(4000),
modified_time TIMESTAMP
)'
);
I know that it's kinda redundant to pass table name twice, but I think that's the easiest here.
Hope somebody finds above useful :-).
Please try:
SET SERVEROUTPUT ON
DECLARE
v_emp int:=0;
BEGIN
SELECT count(*) into v_emp FROM dba_tables where table_name = 'EMPLOYEE';
if v_emp<=0 then
EXECUTE IMMEDIATE 'create table EMPLOYEE ( ID NUMBER(3), NAME VARCHAR2(30) NOT NULL)';
end if;
END;
declare n number(10);
begin
select count(*) into n from tab where tname='TEST';
if (n = 0) then
execute immediate
'create table TEST ( ID NUMBER(3), NAME VARCHAR2 (30) NOT NULL)';
end if;
end;
My solution is just compilation of best ideas in thread, with a little improvement.
I use both dedicated procedure (#Tomasz Borowiec) to facilitate reuse, and exception handling (#Tobias Twardon) to reduce code and to get rid of redundant table name in procedure.
DECLARE
PROCEDURE create_table_if_doesnt_exist(
p_create_table_query VARCHAR2
) IS
BEGIN
EXECUTE IMMEDIATE p_create_table_query;
EXCEPTION
WHEN OTHERS THEN
-- suppresses "name is already being used" exception
IF SQLCODE = -955 THEN
NULL;
END IF;
END;
BEGIN
create_table_if_doesnt_exist('
CREATE TABLE "MY_TABLE" (
"ID" NUMBER(19) NOT NULL PRIMARY KEY,
"TEXT" VARCHAR2(4000),
"MOD_TIME" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
');
END;
/
Any solution which relies on testing before creation can run into a 'race' condition where another process creates the table between you testing that it does not exists and creating it. - Minor point I know.
-- checks for table in specfic schema:
declare n number(10);
begin
Select count(*) into n from SYS.All_All_Tables where owner = 'MYSCHEMA' and TABLE_NAME = 'EMPLOYEE';
if (n = 0) then
execute immediate
'create table MYSCHEMA.EMPLOYEE ( ID NUMBER(3), NAME VARCHAR2(30) NOT NULL)';
end if;
end;
Well there are lot of answeres already provided and lot are making sense too.
Some mentioned it is just warning and some giving a temp way to disable warnings. All that will work but add risk when number of transactions in your DB is high.
I came across similar situation today and here is very simple query I came up with...
declare
begin
execute immediate '
create table "TBL" ("ID" number not null)';
exception when others then
if SQLCODE = -955 then null; else raise; end if;
end;
/
955 is failure code.
This is simple, if exception come while running query it will be suppressed. and you can use same for SQL or Oracle.
Its no need declare and count apply too.
begin
for rec in (select 1 from user_tables where table_name = 'YOUR_TABLE')
-- or
-- for rec in (select 1 from all_tables where table_name = 'YOUR_TABLE' and owner = 'YOU')
loop
execute immediate 'create table your_table as (f1 char(1))';
end loop;
end;
/
Will be good mode create check function
create or replace function this_object_exists (p_obj_name user_objects.object_name%type) return boolean
is
begin
for rec in (select 1 from user_objects where object_name = upper(p_obj_name))
loop
return true;
end loop;
return false;
end this_object_exists;
And thus use code for check exists
.
.
.
.
INDEX PARTITION
TABLE SUBPARTITION
SEQUENCE
TABLE PARTITION
PROCEDURE
LOB PARTITION
LOB
INDEX SUBPARTITION
PACKAGE
PACKAGE BODY
TYPE BODY
TRIGGER
INDEX
TABLE
VIEW
FUNCTION
SYNONYM
TYPE
JOB
...
begin
if not this_object_exists('your_table') then
execute immediate 'create table your_table as (f1 char(1))';
end if;
end;
or
begin
if this_object_exists('your_table') then
execute immediate 'drop table your_table';
end if;
execute immediate 'create table your_table as (f1 char(1))';
end;

Resources