Plsql - Evaluate a dynamic string - oracle

I need to build a process that creates tables dynamically.
I have this:
declare
type array is table of varchar2(30) index by binary_integer;
a array;
expression varchar2(2000);
RESUME_create LONG;
procedure createTables ( texto in VARCHAR2 ) is
begin
dbms_output.put_line('the parameter is: ' || texto);
expression := 'begin ' || texto || '; end;';
dbms_output.put_line(expression);
execute immediate expression;
end;
RESUME_create := 'CREATE TABLE RESUME (
R_Resume_date DATE DEFAULT SYSDATE NOT NULL ,
R_Resume_source CHAR(3) DEFAULT ''001'' NOT NULL ,
R_Resume_channel CHAR(3) DEFAULT ''001'' NOT NULL )';
createTables('RESUME_create');
end;
/
So this is just an example.
So imagine that I need to declare multiples CREATE TABLEs and call the createTable into a loop passing multiples string that the function has to evaluate and execute.

If I un understand well, you need to run a set of DDL statements stored in a collection. If so, you can use something like:
declare
type tArray is table of varchar2(1000) index by pls_integer;
vArray tArray ;
begin
vArray(1) := 'create table firstTab ( a number, b number)';
vArray(2) := 'create table secondTab ( c number, d varchar2(10))';
--
for i in vArray.first .. vArray.last loop
execute immediate vArray(i);
end loop;
end;
/

Related

Oracle stored procedure SQL query assign to variable

I have a column containing a SQL query, and I want to assign a record to a different table according to the result of this query. My code is as below.
Can I make such an assignment in the stored procedure?
l_sel_sql := l_sel_query_rec.SQL_QUERY;
sql_query content: apostrophe included
'SELECT MBB_NO AS mbb_no, clob_column AS CLOB1, JSON_OBJECT(MBB_NO RETURNING CLOB) AS DEL_DATA FROM A '
My code is like this:
create or replace procedure SP_DELETED
is
TYPE sql_query_type IS RECORD
(
TABLE_NAME VARCHAR2(100),
COLUMN_NAME VARCHAR2(25),
SQL_QUERY VARCHAR2(2500)
);
TYPE json_data_type IS RECORD
(
MBB_NO NUMBER,
CLOB1 clob,
CLOB2 CLOB,
DEL_DATA CLOB
);
l_sel_query VARCHAR2(2500);
l_sel_query_cur SYS_REFCURSOR;
l_sel_query_rec sql_query_type;
l_sel_sql VARCHAR2 (2500);
l_deleted_cur SYS_REFCURSOR;
l_deleted_rec json_data_type;
r_del_data KV_DELETED%ROWTYPE;
begin
l_sel_query := 'SELECT TABLE_NAME, COLUMN_NAME , SQL_QUERY FROM STORE_PROCEDURE ';
open l_sel_query_cur for l_sel_query;
loop
fetch l_sel_query_cur into l_sel_query_rec;
exit when l_sel_query_cur%notfound;
*l_sel_sql := l_sel_query_rec.SQL_QUERY;*
open l_deleted_cur for l_sel_sql;
loop
fetch l_deleted_cur into l_deleted_rec;
exit when l_deleted_cur%notfound;
r_del_data.TABLE_NAME := l_sel_query_rec.TABLE_NAME;
r_del_data.COLUMN_NAME := l_sel_query_rec.COLUMN_NAME;
r_del_data.TABLE_KEY := l_deleted_rec.MBB_NO;
r_del_data.DELETED_DOCUMENT_JSON := l_deleted_rec.DEL_DATA;
r_del_data.DELETED_DOCUMENT_CLOB1 := l_deleted_rec.CLOB1;
Insert into KV_DELETED
values r_del_data;
end loop;
close l_deleted_cur;
end loop;
close l_sel_query_cur;
end;

Is there a fast PLSQL function for returning a comma-delimited list of column names for a given schema.table?

I'm trying to set up some simple utilities in a PL/SQL environment. Eventually, I'd expect the hardcoded MYTABLE to be replaced by a bind variable.
I've started with the following, which returns an error:
DECLARE
TYPE colNames_typ IS TABLE OF all_tab_cols.column_name%type index by PLS_INTEGER;
v_ReturnVal colNames_typ;
v_sql VARCHAR2(32000);
BEGIN
v_sql :='SELECT column_name FROM all_tab_cols WHERE table_name = ''MYTABLE'' ' ;
EXECUTE IMMEDIATE (v_sql)
INTO v_returnVal;
-- Convert assoc array to a comma delimited list.
END;
The error returned:
PLS-00597: expression 'V_RETURNVAL' in the INTO list is of wrong type
I cant think of a more 'right' type than a table of entries with the exact same variable type as the source.
Any help would be awesome!
Thanks
Is there a fast PLSQL function for returning a comma-delimited list of column names for a given schema.table?
Use LISTAGG:
DECLARE
v_owner ALL_TAB_COLUMNS.OWNER%TYPE := 'SCHEMA_NAME';
v_table_name ALL_TAB_COLUMNS.TABLE_NAME%TYPE := 'TEST_DATA';
v_columns VARCHAR2(32000);
BEGIN
SELECT LISTAGG( '"' || column_name || '"', ',' )
WITHIN GROUP ( ORDER BY COLUMN_ID )
INTO v_columns
FROM all_tab_columns
WHERE owner = v_owner
AND table_name = v_table_name;
DBMS_OUTPUT.PUT_LINE( v_columns );
END;
/
(Note: you also need to pass the owner or, if you have two tables with identical names in different schemas then, you will get columns for both.)
(Note 2: I am assuming you want a list of column names to put into a dynamic query; if so, then you want to surround the column identifiers with double-quotes. If you don't and a column identifier is case-sensitive then you will get an incorrect name as Oracle will implicitly convert unquoted identifiers to upper case when it parses them in a query. If you don't want the quotes then use SELECT LISTAGG( column_name, ',' ).)
Which, if you have the table:
CREATE TABLE test_data (
A NUMBER,
B DATE,
C VARCHAR2(20),
E TIMESTAMP,
Z INTERVAL DAY TO SECOND,
Q CHAR(5)
);
Outputs:
"A","B","C","E","Z","Q"
db<>fiddle here
Not sure if this is what is being asked:
create or replace function get_cols_string(target_owner all_tab_cols.owner%type, target_table_name all_tab_cols.table_name%type) return varchar2 is
outputString varchar2(32767);
oneMore boolean := false;
BEGIN
for current_col in
(SELECT column_name FROM all_tab_cols
WHERE owner = target_owner and table_name = target_table_name) loop
if(oneMore) then
outputString := outputString || ', ';
end if;
outputString := outputString || current_col.column_name;
oneMore := TRUE;
end loop;
return outputString;
END;
/
Rem Test the above with simple cases
create table tab1 (c1 number);
create table tab2 (c1 number, c2 number);
set serveroutput on
declare
owner_name varchar2(32767) := 'SYS';
table_name varchar2(32767) := 'TAB1';
begin
dbms_output.put_line('For: ' || owner_name || '.' || table_name);
dbms_output.put_line(get_cols_string(owner_name, table_name));
end;
/
declare
owner_name varchar2(32767) := 'SYS';
table_name varchar2(32767) := 'TAB2';
begin
dbms_output.put_line('For: ' || owner_name || '.' || table_name);
dbms_output.put_line(get_cols_string(owner_name, table_name));
end;
/
declare
owner_name varchar2(32767) := 'SYS';
table_name varchar2(32767) := 'ALL_TAB_COLS';
begin
dbms_output.put_line('For: ' || owner_name || '.' || table_name);
dbms_output.put_line(get_cols_string(owner_name, table_name));
end;
/
You asked "is there a reason why the previous approach failed" - well yes. The error stems from Oracle being a very strict typing thus making your assumption that there is not "a more 'right' type than a table of entries with the exact same variable type as the source" false. A collection (table) of type_a is not a variable type_a. You attempted to store a variable of type_a into a collection of type_a, thus giving you the wrong type exception.
Now that does not mean you were actually far off. You wanted to store a collection of type_a variables, returned by the select, into a collection of type_a. You can do that, you just need to let Oracle know. You accomplish it with BULK COLLECT INTO. The following shows that process and creates your CSV of column names.
Note: #MTO posted the superior solution, this just shows you how your original could have been accomplished. Still it is a useful technique to keep in your bag of tricks.
declare
type colnames_typ is table of all_tab_cols.column_name%type;
k_comma constant varchar2(1) := ',';
v_returnval colnames_typ;
v_sql varchar2(32000);
v_sep varchar2(1) := ' ';
v_csv_names varchar2(512);
begin
v_sql := 'select column_name from all_tab_cols where table_name = ''MYTABLE'' order by column_id';
execute immediate (v_sql)
bulk collect into v_returnval;
-- Convert assoc array to a comma delimited list.
for indx in 1 .. v_returnval.count
loop
v_csv_names := v_csv_names || v_sep || v_returnval(indx);
v_sep :=k_comma;
end loop;
v_csv_names := trim(v_csv_names);
dbms_output.put_line(v_csv_names);
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

Update statement error in SQL procedure

I am writing a stored procedure to update data in a table where I will pass a string with the new data (col1='new values', col2='new 2 values'). But when I am compiling my stored procedure , i am getting an error :- "missing equal sign".
Even i tried doing it in a different way (commented code in proc) but that is also giving an error.
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(update_values IN VCHAR2,myid IN INT)
sqlStmt VARCHAR2(1024);
BEGIN
UPDATE MY_TEST_TABLE SET update_values WHERE (TEST_Id = myid);
--sqlStmt := 'UPDATE MY_TEST_TABLE SET ' || update_values || ' WHERE TEST_Id = ' ||myid ;
-- EXECUTE sqlStmt;
END;
Try this (untested):
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(update_values IN VARCHAR2, myid IN NUMBER) AS
sqlStmt VARCHAR2(1024);
BEGIN
sqlStmt := 'UPDATE MY_TEST_TABLE SET ' || update_values || ' WHERE TEST_Id = ' || myid;
EXECUTE IMMEDIATE sqlStmt;
END;
/
The datatype of your first parameter should be VARCHAR2 (maybe just a typo in your post)
Syntax of simple update statement in Oracle is:
Update <table_name>
set <column_name> = some_value
where <conditions..>
You update statement is missing = some_value part that you need to provide.
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(P_update_values IN CHARVAR2, p_myid IN INT)
BEGIN
UPDATE MY_TEST_TABLE
SET col1 = p_update_values
WHERE TEST_Id = p_myid;
END;
/
Using Dynamic SQL, although not required in this case:
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(p_update_values IN VARCHAR2, p_myid IN NUMBER) AS
sqlStmt VARCHAR2(1024);
BEGIN
sqlStmt := 'UPDATE MY_TEST_TABLE SET col1 = :a WHERE TEST_Id = :b';
EXECUTE IMMEDIATE sqlStmt USING p_update_values, p_myid;
END;
/
Things to be noted:
1) Always use meaningful and different names that are other than column names for the parameters.
2) Always use bind variables, :a and :b in above examples, to avoid SQL Injections and improve overall performance if you are going to call this procedure multiple times.

Create nested procedure in Oracle 10g

I want to create a nested procedure where in the 1st procedure, I want to create a table dynamically with 2 columns and in the 2nd procedure, I want to insert values into that table.
Below is the code I am trying to use; what am I doing wrong?
CREATE or replace PROCEDURE mytable (tname varchar2)
is
stmt varchar2(1000);
begin
stmt := 'CREATE TABLE '||tname || '(sname varchar2(20) ,sage number (4))';
execute immediate stmt;
end;
create PROCEDURE mytable1 (emp_name varchar2,emp_age number,tname varchar2)
is
stmt1 varchar2(1000);
begin
stmt1 := 'insert into '||tname||' values ('Gaurav' ,27)';
execute immediate stmt1;
end;
There's no need to create a nested procedure here. You can do everything in a single procedure.
Note my use of bind variables in the execute immediate statement
create or replace procedure mytable (
Ptable_name in varchar2
, Pemp_name in varchar2
, Pemp_age in number
) is
begin
execute immediate 'create table ' || Ptable_name
|| ' (sname varchar2(20), sage number (4))';
execute immediate 'insert into ' || Ptable_name
|| ' values (:emp_name, :emp_age)'
using Pemp_name, Pemp_age;
end;
More generally, there's no need to use execute immediate at all; creating tables on the fly is indicative of a poorly designed database. If at all possible do not do this; create the table in advance and have a simple procedure to insert data, should you need it:
create or replace procedure mytable (
, Pemp_name in varchar2
, Pemp_age in number
) is
begin
insert into my_table
values (Pemp_name, Pemp_age);
end;
I would highly recommend reading Oracle's chapter on Guarding Against SQL Injection.
If you really feel like you have to do this as a nested procedure it would look like this; don't forget to call the nested procedure in the main one as the nested procedure isn't visible outside the scope of the first.
create or replace procedure mytable (
Ptable_name in varchar2
, Pemp_name in varchar2
, Pemp_age in number
) is
procedure myvalues (
Pemp_name in varchar2
, Pemp_age in number
) is
begin
execute immediate 'insert into ' || Ptable_name
|| ' values (:emp_name, :emp_age)'
using Pemp_name, Pemp_age;
end;
begin
execute immediate 'create table ' || Ptable_name
|| ' (sname varchar2(20), sage number (4))';
myvalues ( Pemp_name, Pemp_age);
end;
Please see Oracle's documentation on PL/SQL subprograms

Resources