temporary storage in Oracle? - 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;

Related

Use Input Parameter of procedure to declare %Rowtype record

I am trying to insert the data into a table using Bulk Collect.
Here is the code:
create or replace procedure insert_via_bulk_collect authid current_user
as
lc_status number;
cursor lc_old_tb_data is select * from old_table_name;
type lc_old_tb_type is table of old_table_name%rowtype;
lc_old_tb_row lc_old_tb_type;
begin
open lc_old_tb_data;
loop
fetch lc_old_tb_data bulk collect into lc_old_tb_row;
forall i in 1..lc_old_tb_row.count
insert into new_table_name values lc_old_tb_row(i);
commit;
exit when lc_old_tb_data%notfound;
end loop;
close lc_old_tb_data;
end insert_via_bulk_collect;
It's working for me. But I want to pass the table name dynamically.
like
insert_via_bulk_collect(new_tb_nm varchar2(30), old_tb_name varchar2(30))
But I am not able to use those variables in cursor and declaring the record of OLD_TABLE%rowtype.
Is there a way to do this?
Try below code
CREATE OR REPLACE PROCEDURE INSERT_VIA_BULK_COLLECT
(NEW_TABLE_NAME varchar2, OLD_TABLE_NAME varchar2)
as
l_str varchar2(4000);
begin
l_str := 'declare
CURSOR lc_old_tb_data IS SELECT * FROM ' || OLD_TABLE_NAME || ';
TYPE lc_old_tb_type IS TABLE OF '|| OLD_TABLE_NAME || '%rowtype;
lc_old_tb_row lc_old_tb_type;
begin
Open lc_old_tb_data;
loop
fetch lc_old_tb_data bulk collect into lc_old_tb_row;
FORALL i IN 1..lc_old_tb_row.count
INSERT INTO ' || NEW_TABLE_NAME || ' VALUES lc_old_tb_row(i);
COMMIT;
exit when lc_old_tb_data%notfound;
end loop;
close lc_old_tb_data;
end; ';
dbms_output.put_line (l_str);
EXECUTE IMMEDIATE l_str;
end insert_via_bulk_collect;
You can add Exception handing. Also it is recommended to add limit for fetching of data as below fetch lc_old_tb_data bulk collect into lc_old_tb_row l_size; you can put l_size value as per your requirement.
As mentioned in comments, it is better to use direct insert ... select .... In your case, you can combine this way with execute immediate:
create or replace procedure insert_via_bulk_collect(new_tb_nm varchar2, old_tb_name varchar2) is
begin
execute immediate 'insert into ' || new_tb_nm ||
'(<columns list>) select <columns list> from ' || old_tb_nm;
end;

Oracle Function Select * from Query Not giving any Results

The following is the function definition I am using:
CREATE OR REPLACE FUNCTION MA_FACTSET.totalCustomers
RETURN number as
total INTEGER := 0;
FID VARCHAR2(30) := 'NT33H0-S-US';
stmt varchar2(1000);
BEGIN
execute immediate 'TRUNCATE TABLE MovingAverage';
execute immediate 'DROP TABLE MovingAverage';
execute immediate 'CREATE GLOBAL TEMPORARY TABLE MovingAverage
(ID INTEGER,
PDATE DATE,
PPRICE FLOAT,
MA1 FLOAT) ON COMMIT PRESERVE ROWS';
stmt := 'INSERT INTO MovingAverage (ID,PDATE,PPRICE) SELECT
ROWNUM,"DATE",P_PRICE FROM FP_BASIC_BD WHERE FS_PERM_SEC_ID = ''NT33H0-S-US''';
DBMS_OUTPUT.PUT_LINE(stmt);
execute immediate stmt;
dbms_output.put_line(SQL%ROWCOUNT);
commit;
execute immediate 'UPDATE MovingAverage A SET MA1 =(SELECT AVG(PPRICE)
FROM MovingAverage WHERE ID>=A.ID-5 AND ID<A.ID )' ;
dbms_output.put_line(SQL%ROWCOUNT);
commit;
execute immediate 'SELECT * FROM MovingAverage ORDER BY ID ASC';
dbms_output.put_line(SQL%ROWCOUNT);
commit;
dbms_output.put_line(total);
RETURN total;
END;
/
I have applied some checks to see whether the function is running properly or not, like getting SQL%ROWCOUNT.
The following is the DBMS output of the function:
INSERT INTO MovingAverage (ID,PDATE,PPRICE) SELECT ROWNUM,"DATE",P_PRICE FROM FP_BASIC_BD WHERE FS_PERM_SEC_ID = 'NT33H0-S-US'
4114
4114
0
0
I want know why rowcount of "SELECT * FROM MovingAverage ORDER BY ID ASC" is 0. And if there is any way to print complete table in Data Grid. I am using TOAD for Oracle.
I suggest you to rewrite your function removing dynamic sqls where not necessary. Also, If you have DDLs, DMLs don't use PL/SQL function, rather use procedure.
For select Statements in EXECUTE IMMEDIATE, it will work if you use BULK COLLECT INTO
CREATE OR REPLACE FUNCTION totalCustomers
RETURN NUMBER
AS
total INTEGER := 0;
FID VARCHAR2 (30) := 'NT33H0-S-US';
stmt VARCHAR2 (1000);
TYPE mrectype IS TABLE OF MovingAverage%ROWTYPE;
mrec mrectype;
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE MovingAverage';
EXECUTE IMMEDIATE 'DROP TABLE MovingAverage';
EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE MovingAverage
(ID INTEGER,
PDATE DATE,
PPRICE FLOAT,
MA1 FLOAT) ON COMMIT PRESERVE ROWS';
stmt :=
'INSERT INTO MovingAverage (ID,PDATE,PPRICE) SELECT
ROWNUM,"DATE",P_PRICE FROM FP_BASIC_BD WHERE FS_PERM_SEC_ID = ''NT33H0-S-US''';
DBMS_OUTPUT.PUT_LINE (stmt);
EXECUTE IMMEDIATE stmt;
DBMS_OUTPUT.put_line (SQL%ROWCOUNT);
COMMIT;
EXECUTE IMMEDIATE 'UPDATE MovingAverage A SET MA1 =(SELECT AVG(PPRICE)
FROM MovingAverage WHERE ID>=A.ID-5 AND ID<A.ID )';
DBMS_OUTPUT.put_line (SQL%ROWCOUNT);
COMMIT;
EXECUTE IMMEDIATE 'SELECT * FROM MovingAverage ORDER BY ID ASC'
BULK COLLECT INTO mrec;
DBMS_OUTPUT.put_line (SQL%ROWCOUNT);
COMMIT;
DBMS_OUTPUT.put_line (total);
RETURN total;
END;
/
Call this function like this.
SET SERVEROUTPUT ON
DECLARE
x NUMBER;
BEGIN
x := totalCustomers;
END;
/
You cannot call the function in SQL select
With collections and the table function, a function can return a table that can be queried in an SQL statement. This is demonstrated in the following example.
Create your record type variable first.
create or replace type t_record as object (i number,n varchar2(30));
Create table type variable which is based on record type variable.
create or replace type t_table as table of t_record;
Then we can proceed to create a function as follows.
create or replace function return_table return t_table as
v_ret t_table;
begin
--
-- Call constructor to create the returned
-- variable:
--
v_ret := t_table();
--
-- Add one record after another to the returned table.
-- Note: the »table« must be extended before adding
-- another record:
--
v_ret.extend; v_ret(v_ret.count) := t_record(1, 'one' );
v_ret.extend; v_ret(v_ret.count) := t_record(2, 'two' );
v_ret.extend; v_ret(v_ret.count) := t_record(3, 'three');
--
-- Return the record:
--
return v_ret;
end return_table;
For more details and further extension of work please refer following URL.
https://renenyffenegger.ch/notes/development/databases/Oracle/PL-SQL/collection-types/return-table-from-function/index
http://www.adp-gmbh.ch/ora/plsql/coll/return_table.html

Create a table with oracle pl/sql

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

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.

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