Create a table with oracle pl/sql - oracle

I get this error ORA-00933: SQL command not properly ended, but I cannot see what the problem is.
Creates the package:
CREATE OR REPLACE PACKAGE "TOOL_PKG" IS
PROCEDURE CREATE_ZTEMP_CLAIMS_TABLES;
END TOOL_PKG;
/
Create a procedure for the package. I get the error trying to run the procedure.
What I want to do is create a new table with an existing table. I need it dynamic because I'll be adding a lot more tables to the list.
CREATE OR REPLACE PACKAGE BODY CUBS_OWNER."TOOL_PKG" IS
PROCEDURE CREATE_NEW_TABLE IS
type array_t is table of varchar2(50) index by pls_integer;
my_table_t array_t;
elem varchar2(50);
c int;
BEGIN
my_table_t (1) := 'MY_TABLE';
elem := my_table_t.first;
while elem is not null loop
select count(*) into c from user_tables where table_name = upper('ZTEST_'||my_table_t(elem));
if c=0 then
Begin
execute immediate 'CREATE TABLE upper(NEWX_'||my_table_t(elem)||') AS SELECT * FROM upper('||my_table_t(elem)||') where rownum=0';
COMMIT;
DBMS_OUTPUT.put_line ('ZTEST_'||my_table_t(elem)||' created.');
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (
'tool_pkg.create_new_table...failed creating table '
|| CHR (10)
|| SQLERRM);
END;
end if;
elem := my_table_t.next(elem);
end loop;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (
'tool_pkg.create_new_table...failed creating table '
|| CHR (10)
|| SQLERRM);
END;
END tool_pkg;
/

I had to make sure the methods upper were outside the quotes.
Instead of this
execute immediate 'CREATE TABLE upper(NEWX_'||my_table_t(elem)||') AS SELECT * FROM upper('||my_table_t(elem)||') where rownum=0';
It had to be in this form
execute immediate 'CREATE TABLE '||upper('NEWX_'||my_table_t(elem))||' AS SELECT * FROM '||upper(my_table_t(elem))||' where rownum=0';

Related

how to provide multiple bind variable for execute immediate inside pl/sql block

I have a table which contains the metadata of the table. The task is to periodically delete a specific set of tables, provided the information for where condition and how many days the data is retained are present.If a user needs to delete a data on daily basis, he simply enter his table name in audit log. The procedure will do the rest. The example is shown below.
Table structure:
CREATE TABLE delete_tbl_list (
id NUMBER NOT NULL,
table_name VARCHAR2(100) NOT NULL,
column_name VARCHAR2(100) NOT NULL,
day_retented NUMBER NOT NULL,
where_clause VARCHAR2(2000)
);
the day_retended is the number which will tell on how many days the data can hold.
select * from delete_tbl_list
ID TABLE_NAME COLUMN_NAME DAY_RETENTED WHERE_CLAUSE
---------- -----------------------------------------------
1 audit_log log_TS 60
So if i need to delete a table taking log_ts(timestamp) as column with 60days period as retention. The table in query needs to do
delete * from audit_log where log_ts<systimestamp -60
Now i need to do it using bulk delete and more dynamic and hence i wrote the procedure below,
create procedure dynamic_mass_delete as
TYPE tbl_rec_rowid IS TABLE OF ROWID INDEX BY PLS_INTEGER;
lv_del_exec_rec tbl_rec_rowid;
v_limit PLS_INTEGER := 10000;
m_date date:=sysdate;
total_records_deleted number:=0;
l_where delete_tbl_list.where_clause%type;
l_sql varchar2(2000);
TYPE ref_cur_type IS REF CURSOR;
delete_content ref_cur_type;
BEGIN
for i in (select table_name,COLUMN_NAME,DAY_RETENTED,WHERE_CLAUSE from delete_tbl_list) loop
DBMS_OUTPUT.PUT_LINE('tablename..'||i.table_name);
l_where:='';
m_date:=m_date-i.day_retented;
if i.where_clause is not null then
l_where:=' and '||i.where_clause;
end if;
OPEN delete_content FOR 'SELECT rowid from ' || i.table_name ||' where '|| i.COLUMN_NAME || ' <= to_timestamp('''||m_date||''')'||l_where;
LOOP
total_records_deleted := 0;
FETCH delete_content BULK COLLECT INTO lv_del_exec_rec LIMIT v_limit;
FORALL j IN lv_del_exec_rec.first..lv_del_exec_rec.last
execute immediate 'DELETE FROM :1 where rowid=:2 'using i.table_name,lv_del_exec_rec(j);
total_records_deleted := total_records_deleted + SQL%rowcount;
DBMS_OUTPUT.PUT_LINE('Delete count..'||total_records_deleted);
EXIT WHEN delete_content%notfound;
END LOOP;
CLOSE delete_content;
end loop;
EXCEPTION
when others then
DBMS_OUTPUT.PUT_LINE('Error-->'||SQLERRM);
END;
/
Now i getting error in the delete query stating invalid table name, i was not able to write dbms_output inside for all statment. Is it possible to use multiple bind variable inside a pl/sql procedure.
The error which i get is ,
Error-->ORA-00942: table or view does not exist
PL/SQL procedure successfully completed.
The table very much exists, but it is throwing error, i was not able to print inside the forall block too.
Switch to
execute immediate 'DELETE FROM ' || i.table_name ||' where rowid = ' || lv_del_exec_rec(j);
You can simplify your code as follows:
BEGIN
FOR TBL_DETAILS IN (
SELECT
TABLE_NAME,
COLUMN_NAME,
DAY_RETENTED,
WHERE_CLAUSE
FROM
DELETE_TBL_LIST
) LOOP
DBMS_OUTPUT.PUT_LINE('tablename..' || TBL_DETAILS.TABLE_NAME);
EXECUTE IMMEDIATE 'DELETE FROM '
|| TBL_DETAILS.TABLE_NAME
|| ' WHERE '
|| TBL_DETAILS.COLUMN_NAME
|| ' < SYSTIMESTAMP - '
|| TBL_DETAILS.DAY_RETENTED
|| CASE
WHEN TBL_DETAILS.WHERE_CLAUSE IS NOT NULL THEN ' AND ' || TBL_DETAILS.WHERE_CLAUSE
END;
DBMS_OUTPUT.PUT_LINE('Delete count..' || SQL%ROWCOUNT);
END LOOP;
END;
/
Hope, This will help you in creating simpler code.
Cheers!!

PL SQL Package to take backup of all tables in a schema

I am trying to understand how i can proceed with a PL SQL Package that i can use to take backup of all the tables in a specific database as i need to keep this mandatory backup. while taking the backup i need to prefix the table names with bak_XX...
Can someone suggest please on how i can start of as i am not very familiar with PL SQL in oracle.
Thank You
I have started off with below script
CREATE OR REPLACE PACKAGE BODY backup_scr AS
FUNCTION get_tablenames(db_name VARCHAR2) RETURN VARCHAR2 IS
v_dbnames VARCHAR2(150);
BEGIN
SELECT OBJECT_NAME || ','
INTO v_dbnames
FROM SYS.ALL_OBJECTS
WHERE UPPER(OBJECT_TYPE) = 'PACKAGE';
RETURN v_dbnames;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
WHEN TOO_MANY_ROWS THEN
RETURN NULL;
END;
Instead of function, write a procedure inside the PACKAGE BODY like this. Get the list of tables from ALL_TABLES , loop through it and dynamically run CREATE TABLE for backup table creation.
Package Specification.
create or replace PACKAGE backup_scr AS
PROCEDURE p_backup_scr (
p_ret_code OUT NUMBER
) ;
END backup_scr ;
/
Body
CREATE OR REPLACE PACKAGE BODY backup_scr AS
PROCEDURE p_backup_scr (
p_ret_code OUT NUMBER
)
IS
BEGIN
FOR tabs IN (
SELECT
owner,
table_name
FROM
all_tables
) LOOP
BEGIN
dbms_output.put_line('CREATING BACKUP OF '
|| tabs.owner
|| '.'
|| tabs.table_name);
EXECUTE IMMEDIATE 'CREATE TABLE BAK_'
|| tabs.table_name
|| ' AS SELECT * FROM '
|| tabs.owner
|| '.'
|| tabs.table_name;
dbms_output.put_line('COMPLETED SUCCESSFULLY');
p_ret_code := 0;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
p_ret_code := 1;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
p_ret_code := 1;
END p_backup_scr;
END backup_scr;
/
You can then call your script from sqlplus/ sql developer as
SET SERVEROUTPUT ON;
variable return_code NUMBER;
EXEC backup_scr.p_backup_scr(:return_code);
PRINT return_code;
Note: You can add a where condition for specific schema in the select for all_tables. And if you want it to be more generic, then pass the schema name as procedure argument.

How can I make an entire PL/SQL code block dynamic with bind variables?

Background
I'm trying to make a re-usable PL/SQL procedure to move data from one
database to another.
For this purpose, I'm using dynamic SQL.
The procedure executes perfectly if I use a REPLACE with placeholders.
However, for security reasons, I want to use bind variables.
Question
How can I make an entire PL/SQL code block dynamic (with bind
variables)? If I use a REPLACE instead of the bind variables, it works
fine.
How to replicate
To replicate this in your database, create the following procedure as it is:
create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);
l_cursor_limit pls_integer := 500;
l_values_list varchar2(32767);
begin
select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
table_name = i_table_name and
virtual_column = 'NO';
l_sql := q'[
declare
l_cur_limit pls_integer := :l_cursor_limit;
cursor c_get_to_be_moved is
select :i_table_name.*, :i_table_name.rowid
from :i_table_name;
type tab_to_be_moved is table of c_get_to_be_moved%rowtype;
l_to_be_moved tab_to_be_moved;
begin
open c_get_to_be_moved;
loop
fetch c_get_to_be_moved
bulk collect into l_to_be_moved limit l_cur_limit;
exit when l_to_be_moved.count = 0;
for i in 1.. l_to_be_moved.count loop
begin
insert into :i_table_name#:i_destination values (:l_values_list);
exception
when others then
dbms_output.put_line(sqlerrm);
l_to_be_moved.delete(i);
end;
end loop;
forall i in 1.. l_to_be_moved.count
delete
from :i_table_name
where rowid = l_to_be_moved(i).rowid;
for i in 1..l_to_be_moved.count loop
if (sql%bulk_rowcount(i) = 0) then
raise_application_error(-20001, 'Could not find ROWID to delete. Rolling back...');
end if;
end loop;
commit;
end loop;
close c_get_to_be_moved;
exception
when others then
rollback;
dbms_output.put_line(sqlerrm);
end;]';
execute immediate l_sql using l_cursor_limit, i_table_name, i_destination, l_values_list;
exception
when others then
rollback;
dbms_output.put_line(sqlerrm);
end;
/
And then you can execute the procedure with the following:
begin
move_data('MySchemaName', 'MyTableName', 'MyDatabaseLinkName');
end;
/
Due to many reasons(inability to generate an appropriate execution plan, security checking, etc.) Oracle does not allow identifiers binding (table names, schema names, column names and so on). So if it's really necessary, the only way is to hard code those identifiers after some sort of validation (to prevent SQL injection).
If I understand well, you could try a trick, by using a dynamic SQL inside a dynamic SQL.
setup:
create table tab100 as select level l from dual connect by level <= 100;
create table tab200 as select level l from dual connect by level <= 200;
create table tabDest as select * from tab100 where 1 = 2;
This will not work:
create or replace procedure testBind (pTableName in varchar2) is
vSQL varchar2(32000);
begin
vSQL := 'insert into tabDest select * from :tableName';
execute immediate vSQL using pTableName;
end;
But this will do the trick:
create or replace procedure testBind2 (pTableName in varchar2) is
vSQL varchar2(32000);
begin
vSQL := q'[declare
vTab varchar2(30) := :tableName;
vSQL2 varchar2(32000) := 'insert into tabDest select * from ' || vTab;
begin
execute immediate vSQL2;
end;
]';
execute immediate vSQL using pTableName;
end;
I think you can do it simpler.
create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);
begin
select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
table_name = i_table_name and
virtual_column = 'NO';
l_sql := 'insert into '||i_destination||'.'||i_table_name||' select * from '||i_schema_name||'.'||i_table_name;
execute immediate l_sql;
end;
If you are concerned about SQL-Injection, have a look at package DBMS_ASSERT. This PL/SQL package provides function to validate properties of input values.

temporary storage in Oracle?

I need to store output of a query(which returns multiple rows) temporarily in Oracle. One of the ways to do this is using temporary table and I am using that currently. But the problem with them is that they stay around and cannot be created and dropped in a procedure willy nilly. Is there another object that I can use to temporarily store the output of a query to be able to use in the stored procedure ?
Here is my stored procedure for reference:
create or replace PROCEDURE procPrintOutput
IS
l_stmt VARCHAR2(512) := '';
create_table_stmt VARCHAR2(512) := 'create GLOBAL TEMPORARY TABLE temp(matViewLogQuery CLOB) ON COMMIT DELETE ROWS';
drop_mat_view_logs_stmt VARCHAR2(100) := 'DROP MATERIALIZED VIEW LOG ON ';
drop_temp_table VARCHAR2(512) := 'DROP TABLE temp';
ref_cursor SYS_REFCURSOR;
BEGIN
EXECUTE IMMEDIATE(create_table_stmt);
DBMS_OUTPUT.PUT_LINE('created temp table');
DECLARE
CURSOR LIST_OF_MVL IS SELECT * FROM USER_MVIEW_LOGS;
CURSOR CREATE_MVL IS SELECT * FROM temp;
BEGIN
FOR TEST IN LIST_OF_MVL
LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE('owner : ' || TEST.OWNER || ' - name : ' || TEST.MASTER);
l_stmt := 'insert into temp SELECT SYS.DBMS_METADATA.get_dependent_ddl (''MATERIALIZED_VIEW_LOG'', '''||TEST.MASTER||''', '''||TEST.LOG_OWNER||''') from dual';
EXECUTE IMMEDIATE (l_stmt);
EXECUTE IMMEDIATE (drop_mat_view_logs_stmt || TEST.MASTER);
DBMS_OUTPUT.PUT_LINE('view dropped ... ');
END;
END LOOP;
FOR CREATE_SCRIPT IN CREATE_MVL
LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE('SCRIPT : ' || CREATE_SCRIPT.matViewLogQuery);
EXECUTE IMMEDIATE(CREATE_SCRIPT.matViewLogQuery);
DBMS_OUTPUT.PUT_LINE('view recreated ... ');
END;
END LOOP;
END;
EXECUTE IMMEDIATE(drop_temp_table);
DBMS_OUTPUT.PUT_LINE('Done! Table dropped');
ROLLBACK;
END;
Thanks
instead of of temp table you can use collection in pl/sql and store all information that you need into the collection by BULK COLLECT
here small example
in my example I collect all DDLs for matviews and name of the views, then in simple cycle we can do any operations...
DECLARE
TYPE tDDL IS record (name varchar2(30), ddl CLOB);
TYPE tblDDL IS TABLE OF tDDL INDEX BY PLS_INTEGER;
l_ddls tblDDL;
BEGIN
SELECT object_name, dbms_metadata.get_ddl('MATERIALIZED_VIEW', object_name)
BULK COLLECT INTO l_ddls
from user_objects
where object_type= 'MATERIALIZED VIEW';
FOR indx IN 1 .. l_ddls.COUNT
LOOP
--do what you need with collected DDLs
DBMS_OUTPUT.PUT_LINE('view dropped ... ');
--your complex execute immediate logic here
dbms_output.put(l_ddls(indx).name);
dbms_output.put(' : ');
dbms_output.put_line(substr(l_ddls(indx).ddl, 100));
DBMS_OUTPUT.PUT_LINE('view recreated ... ');
END LOOP;
END;

Oracle : Create Table as Select statement and Select query on created table in Single Stored Procedure

I want Stored Procedure which create Temporary table using Create Table ... Select ... Statement.
And then select the records from same created table.
And finally drop that created table...
I want all these functionality in same stored procedure.
I have create the following stored procedure. But i got the below error
PL/SQL: ORA-00942: table or view does not exist
This one is my Procedure
DECLARE
TEMP_TBL VARCHAR2(4000);
TBL_NAME VARCHAR2(200) := 'ABC_TEST';
BEGIN
TEMP_TBL := 'CREATE TABLE MY_TAB_COL AS(SELECT TABLE_NAME,COLUMN_NAME,DATA_TYPE,to_lob(DATA_DEFAULT) AS DATA_DEFAULT,NULLABLE FROM ALL_TAB_COLS WHERE TABLE_NAME=''' || TBL_NAME || ''')';
DBMS_OUTPUT.PUT_LINE(TEMP_TBL);
EXECUTE IMMEDIATE TEMP_TBL;
FOR DD_COLUMNS IN
(SELECT TABLE_NAME FROM MY_TAB_COL)
LOOP
DBMS_OUTPUT.PUT_LINE('DD_COLUMNS.TABLE_NAME');
END LOOP;
END;
I'm not sure why you want scenario like that, and not reccomend to use this code in production. I'm pretty sure in the 99% of the cases, this approach can be written in other way. But you can try the code bellow(Everything in your procedure should use dynamic sql)
declare
TBL_NAME varchar2(200) := 'ABC_TEST';
lv_Sql varchar2(4000);
begin
lv_Sql := 'CREATE TABLE MY_TAB_COL AS(SELECT TABLE_NAME,COLUMN_NAME,DATA_TYPE,to_lob(DATA_DEFAULT) AS DATA_DEFAULT,NULLABLE FROM ALL_TAB_COLS WHERE TABLE_NAME=''' ||
TBL_NAME || ''')';
DBMS_OUTPUT.PUT_LINE(lv_Sql);
execute immediate lv_Sql;
lv_Sql := 'begin
for DD_COLUMNS in (select TABLE_NAME from MY_TAB_COL) loop
DBMS_OUTPUT.PUT_LINE(DD_COLUMNS.TABLE_NAME);
end loop;
end;';
DBMS_OUTPUT.PUT_LINE(lv_Sql);
execute immediate lv_Sql;
for R in (select *
from user_objects
where object_name = 'MY_TAB_COL'
and object_type = 'TABLE') loop
DBMS_OUTPUT.PUT_LINE('drop table ' || R.Object_Name);
execute immediate 'drop table ' || R.Object_Name;
end loop;
end;

Resources