Oracle stored procedure SQL query assign to variable - oracle

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;

Related

How to pass JSON type data as an input parameter in a Stored procedure

I have one requirement wherein I need to pass JSON as an input parameter and process the data accordingly.
JSON data:
{
"Table name": "test_table",
"Column name":["column_1","column_2"],
"Column_value":["test_data","1"]
}
I need to write a procedure with this JSON as an input parameter.
Then based on the table name and column name it should insert the particular column value into the respective columns of a table.
Pseudo Code:
Store JSON in one table with the table structure as
table_id |Table_name | Column_name | Column_value
Then pass table_name, column_name, and column_values JSON format as input parameters.
Then extract tables, columns, and column_value from the input parameter and load in into the respective table.
Will this work? As I am not aware of how to handle JSON in a stored procedure or if anyone has any clue of how to start with this it would help me.
Tool Used: SQL Developer (18c)
You can use:
CREATE PROCEDURE load_data(
i_json IN CLOB
)
IS
v_obj JSON_OBJECT_T := JSON_OBJECT_T(i_json);
v_tbl VARCHAR2(20) := v_obj.get_string('Table name');
v_cols JSON_ARRAY_T := v_obj.get_Array('Column name');
v_vals JSON_ARRAY_T := v_obj.get_Array('Column_value');
v_sql CLOB := 'INSERT INTO ';
v_sql_vals CLOB := ') VALUES (';
BEGIN
v_sql := v_sql || DBMS_ASSERT.SQL_OBJECT_NAME(
DBMS_ASSERT.ENQUOTE_NAME(v_tbl, FALSE)
);
v_sql := v_sql || ' (';
FOR pos IN 0 .. v_cols.get_size() - 1
LOOP
IF pos > 0 THEN
v_sql := v_sql || ',';
v_sql_vals := v_sql_vals || ',';
END IF;
v_sql := v_sql || DBMS_ASSERT.ENQUOTE_NAME(v_cols.get_string(pos), FALSE);
v_sql_vals := v_sql_vals || DBMS_ASSERT.ENQUOTE_LITERAL(v_vals.get_string(pos));
END LOOP;
v_sql := v_sql || v_sql_vals || ')';
EXECUTE IMMEDIATE v_sql;
END;
/
If you have the table:
CREATE TABLE "test_table" (
"column_1" VARCHAR2(20),
"column_2" NUMBER
);
Then you can use:
BEGIN
load_data('{
"Table name": "test_table",
"Column name":["column_1","column_2"],
"Column_value":["test_data","1"]
}');
END;
/
And the table will contain:
SELECT * FROM "test_table";
column_1
column_2
test_data
1
db<>fiddle here

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;

how to store and call a sql query from a table column?

Insert statement below holds details of another Table that has 3 columns (id,ins_dt,text_stuff)
INSERT INTO swpurge_config
(schema
,table_name
,table_alias
,driving_column
,deletion_predicate
,retention_period_type
,retention_period_value)
VALUES
('CUSTOMERS_OWNER'
,'LAST_NAMES'
,'LN'
,'ins_dt'
,'WHERE ln.ins_dt < SYSDATE - p_retention_period_value
AND ora_hash(ln.rowid, 8) = 1'
,'month'
,'6');
Aim:
I am essentially trying to add a delete predicate in a varchar2 column. The idea is to call this column in a Procedure which will delete records up to 1000 rows:
PROCEDURE delete_rows
(
p_schema IN VARCHAR2
,p_table_name IN VARCHAR2
,p_table_alias in varchar2
,p_retention_period_value IN VARCHAR2
,p_delete_predicate IN VARCHAR2
) IS
v_sql varchar2 (32000);
v_row_limit pls_integer (1000);
BEGIN
v_sql := 'delete from ' || p_schema ||'.'|| table_name ||p_table_alias
'older than '|| p_retention_period_value || p_delete_predicate;
dbms_output.put_line(v_sql);
END delete_rows;
Not sure about 2 things:
1. How to store the sql where clause in a table column?
2. How to execute the where clause as a statement in the procedure?
Thanks
you are talking about Dynamic SQL.
You can just store it inside VARCHAR2 in string format. then retrieve it
select deletion_predicate into v_sql from swpurge_config where ...
then
v_sql := "SELECT ...... FRom .... " || v_sql;
finally execute it
EXECUTE IMMEDIATE v_sql;
if your sql statement also includes parameters then
EXECUTE IMMEDIATE query_str
using param1;

Plsql - Evaluate a dynamic string

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

Returning dataset with PL/SQL and a variable table name

I'm trying to write a PL/SQL function to store a select statement with a variable table name (a bit weird i know but it is actually a good design decision). The following code does not work...but I'm not sure how to both take a variable table name (building the query) and return a dataset. Anyone have any experience in this? TIA.
CREATE OR REPLACE FUNCTION fn_netstat_all (casename in varchar2)
RETURN resultset_subtype
IS
dataset resultset_subtype;
v_sql varchar2(25000);
v_tablename varchar2(50);
begin
v_sql := 'SELECT * FROM ' || casename || '_netstat;';
OPEN dataset FOR
execute immediate v_sql;
return dataset;
end;
If your resultset_subtype is a ref_cursor (or just replace resultset_subtype with a ref_cursor) you could:
CREATE OR REPLACE
FUNCTION fn_netstat_all (
casename IN VARCHAR2
)
RETURN resultset_subtype
IS
dataset resultset_subtype;
BEGIN
OPEN dataset
FOR 'SELECT * FROM ' || casename || '_netstat';
RETURN dataset;
END fn_netstat_all;
FWIW, you might want to look into the DBMS_ASSERT package to wrap the casename variable to help protect against SQL Injection attacks in your dynamic SQL.
Hope it helps...
Below it is assumed all tables are similar. It's also possible to select a subset of colums that are similar in every table without using DBMS_SQL. I have also paid some attention to SQL injection mentioned by Ollie.
create table so9at (
id number(1),
data varchar2(5)
);
insert into so9at values (1, 'A-AAA');
insert into so9at values (2, 'A-BBB');
insert into so9at values (3, 'A-CCC');
create table so9bt (
id number(1),
data varchar2(5)
);
insert into so9bt values (5, 'B-AAA');
insert into so9bt values (6, 'B-BBB');
insert into so9bt values (7, 'B-CCC');
create table secret_identities (
cover_name varchar2(20),
real_name varchar2(20)
);
insert into secret_identities values ('Batman', 'Bruce Wayne');
insert into secret_identities values ('Superman', 'Clark Kent');
/* This is a semi-secure version immune to certain kind of SQL injections. Note
that it can be still used to find information about any table that ends with
't'. */
create or replace function cursor_of (p_table_id in varchar2)
return sys_refcursor as
v_cur sys_refcursor;
v_stmt constant varchar2(32767) := 'select * from ' || dbms_assert.qualified_sql_name(p_table_id || 't');
begin
open v_cur for v_stmt;
return v_cur;
end;
/
show errors
/* This is an unsecure version vulnerable to SQL injection. */
create or replace function vulnerable_cursor_of (p_table_id in varchar2)
return sys_refcursor as
v_cur sys_refcursor;
v_stmt constant varchar2(32767) := 'select * from ' || p_table_id || 't';
begin
open v_cur for v_stmt;
return v_cur;
end;
/
show errors
create or replace procedure print_values_of (p_cur in sys_refcursor) as
type rec_t is record (
id number,
data varchar2(32767)
);
v_rec rec_t;
begin
fetch p_cur into v_rec;
while p_cur%found loop
dbms_output.put_line('id = ' || v_rec.id || ' data = ' || v_rec.data);
fetch p_cur into v_rec;
end loop;
end;
/
show errors
declare
v_cur sys_refcursor;
begin
v_cur := cursor_of('so9a');
print_values_of(v_cur);
close v_cur;
v_cur := cursor_of('so9b');
print_values_of(v_cur);
close v_cur;
/* SQL injection vulnerability */
v_cur := vulnerable_cursor_of('secret_identities --');
dbms_output.put_line('Now we have a cursor that reveals all secret identities. Just see DBMS_SQL.DESCRIBE_COLUMNS ...');
close v_cur;
/* SQL injection made (mostly) harmless - will throw ORA-44004: invalid qualified SQL name */
v_cur := cursor_of('secret_identities --');
close v_cur;
end;
/

Resources