execute immediate with dynamic table name passed to a procedure - oracle

I have a view view_test_dynamic which contains insert statements as a single column as given below.
I need to populate this single column value into a table. example as test_dynamic table as below.
I have multiple views like this and I need to populate into different tables.
When I create procedure in a normal way by declaring cursor for one view it works.
I would like to use dynamic pl/sql by passing the view name to the procedure as below.
I am getting error at execute immediate. the error says " expression is of wrong type"
Can some one please help?
create or replace view view_test_dynamic as
select 'insert into test_dynamic(cust_id,address) values ('||cust_id||','''||address||''')' as trans_out
from
(
select 10 cust_id,'9 Help Street, Level 4' address from dual union all
select 11 cust_id,'22 Victoria Street' address from dual union all
select 12 cust_id,'1495 Franklin Str.' address from dual union all
select 13 cust_id,'30 Hasivim St.,Petah-Tikva' address from dual union all
select 14 cust_id,'2 Jakaranda St' address from dual union all
select 15 cust_id,'61, Science Park Rd' address from dual union all
select 16 cust_id,'61, Social park road.' address from dual union all
select 17 cust_id,'Av. Hermanos Escobar 5756' address from dual union all
select 18 cust_id,'Ave. Hermanos Escobar 5756' address from dual union all
select 19 cust_id,'8000 W FLORISSANT Ave.' address from dual union all
select 20 cust_id,'8600 MEMORIAL PKWY SW' address from dual union all
select 21 cust_id,'8200 FLORISSANTMEMORIALWAYABOVE SW' address from dual union all
select 22 cust_id,'8600 MEMORIALFLORISSANT PKWY SW.' address from dual
) t1;
Table:
create table test_dynamic
(
cust_id number,
address varchar2(100)
);
Procedure:
CREATE OR REPLACE PROCEDURE POP_TABLES_DYNAMIC(p_table_name in varchar2) as
l_cursor sys_refcursor;
l_trans_out dbms_sql.varchar2_table;
l_processed_cnt number := 0;
l_rec varchar2(500);
BEGIN
dbms_output.put_line( 'Started' );
open l_cursor for 'select trans_out from ' || p_table_name;
loop
fetch l_cursor BULK COLLECT into l_trans_out LIMIT 5000;
for i in 1 .. l_trans_out.count
loop
execute immediate l_trans_out using l_trans_out(i);
commit;
l_processed_cnt := l_processed_cnt+1;
end loop;
exit when l_cursor%notfound;
end loop;
close l_cursor;
dbms_output.put_line( 'processed ' || l_processed_cnt || ' records' );
dbms_output.put_line('Ended');
END;
/
sho err;

For sample data you posted:
SQL> select * from view_test_dynamic;
TRANS_OUT
-------------------------------------------------------------------------------------------
insert into test_dynamic(cust_id,address) values (10,'9 Help Street, Level 4')
insert into test_dynamic(cust_id,address) values (11,'22 Victoria Street')
insert into test_dynamic(cust_id,address) values (12,'1495 Franklin Str.')
insert into test_dynamic(cust_id,address) values (13,'30 Hasivim St.,Petah-Tikva')
insert into test_dynamic(cust_id,address) values (14,'2 Jakaranda St')
insert into test_dynamic(cust_id,address) values (15,'61, Science Park Rd')
insert into test_dynamic(cust_id,address) values (16,'61, Social park road.')
insert into test_dynamic(cust_id,address) values (17,'Av. Hermanos Escobar 5756')
insert into test_dynamic(cust_id,address) values (18,'Ave. Hermanos Escobar 5756')
insert into test_dynamic(cust_id,address) values (19,'8000 W FLORISSANT Ave.')
insert into test_dynamic(cust_id,address) values (20,'8600 MEMORIAL PKWY SW')
insert into test_dynamic(cust_id,address) values (21,'8200 FLORISSANTMEMORIALWAYABOVE SW')
insert into test_dynamic(cust_id,address) values (22,'8600 MEMORIALFLORISSANT PKWY SW.')
13 rows selected.
SQL> select * from test_dynamic;
no rows selected
consider such a (simpler) code:
SQL> create or replace procedure pop_tables_dynamic (p_table_name in varchar2)
2 is
3 rc sys_refcursor;
4 l_str view_test_dynamic.trans_out%type;
5 begin
6 open rc for 'select trans_out from ' || dbms_assert.sql_object_name(p_table_name);
7 loop
8 fetch rc into l_str;
9 exit when rc%notfound;
10 execute immediate l_str;
11 end loop;
12 close rc;
13 end;
14 /
Procedure created.
SQL> exec pop_tables_dynamic('view_test_dynamic');
PL/SQL procedure successfully completed.
Result:
SQL> select * from test_dynamic;
CUST_ID ADDRESS
---------- ------------------------------------------------------------
10 9 Help Street, Level 4
11 22 Victoria Street
12 1495 Franklin Str.
13 30 Hasivim St.,Petah-Tikva
14 2 Jakaranda St
15 61, Science Park Rd
16 61, Social park road.
17 Av. Hermanos Escobar 5756
18 Ave. Hermanos Escobar 5756
19 8000 W FLORISSANT Ave.
20 8600 MEMORIAL PKWY SW
21 8200 FLORISSANTMEMORIALWAYABOVE SW
22 8600 MEMORIALFLORISSANT PKWY SW.
13 rows selected.

Related

How can i execute code faster by using PLSQL

----- My code is taking so much time to execute .it takes 1074 seconds to execute. Can someone tell me any way so that I can execute more faster--------
set serveroutput on;
declare
table_or_view_does_not_exist exception;
pragma exception_init(table_or_view_does_not_exist,-00942);
d_table varchar2(200);
q_table varchar2(200);
r_emp SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST();
type t_list is table of all_tab_columns%rowtype index by PLS_INTEGER;
v_array t_list;
begin
begin
d_table:='drop table subs_profile_spcl_char PURGE';
execute immediate d_table;
exception
when table_or_view_does_not_exist then
null;
end;
dbms_output.put_line('Table has been dropped');
q_table:='create table subs_profile_spcl_char
(column_name varchar2(50),
spcl_char_count Number)';
execute immediate q_table;
dbms_output.put_line('Table has been created');
dbms_output.enable;
select /*parallel(14)*/ * bulk collect into v_array from all_tab_columns where table_name='SUBSCRIBER_PROFILE' and OWNER='MIG';
for i in 1..v_array.count() loop
r_emp.extend;
EXECUTE IMMEDIATE
'select /*parallel(16)*/ count(*) from '||v_array(i).table_name||' where not regexp_like ('||v_array(i).column_name||',''[A-za-z0-9.]'')'
into r_emp(i);
if r_emp(i)<>0 then
dbms_output.put_line(v_array(i).column_name||'------------>>>>'||r_emp(i));
execute immediate 'insert into subs_profile_spcl_char values (:param1,:param2)' using v_array(i).column_name,r_emp(i);
end if;
end loop;
end;
The way I see it, a few objections.
you shouldn't drop/create table within PL/SQL; that's just bad practice. Create table at SQL level, then remove its contents (if you have to) in a procedure (truncate is faster than delete, but is irreversible. As you actually dropped the table, I'd say that you can use it)
target table is wrongly created anyway; knowing only the column name is far from enough - you should be storing owner and table name as well
it seems that you're looking for columns that contain "special characters" (i.e. not alphanumerics nor dots); in that case, modify regular expression
no need to scan all owners - SYS, SYSTEM, CTXSYS, possible APEX_ users most probably aren't interesting in what you're doing so - remove them. Remove other owners as well, if you want.
no need to scan all columns - numbers and dates can't contain special characters - filter only CHAR datatype family columns
no need to use r_emp collection. Fetch the count into a scalar variable (l_cnt in my example)
parallel "hint" is wrongly used. The way you put it, it is just a comment, not a hint. Hint looks like e.g. select /*+ parallel */ (you're missing a plus sign)
dbms_output takes resources; remove it if you don't need it (you don't; the result is anyway stored into the table)
insert into the target table doesn't require dynamic SQL so - switch to an ordinary insert
OK, here you go, here's what you might try to do.
(re)create the target table at SQL level:
SQL> DROP TABLE subs_profile_spcl_char;
Table dropped.
SQL> CREATE TABLE subs_profile_spcl_char
2 (
3 owner VARCHAR2 (30),
4 table_name VARCHAR2 (30),
5 column_name VARCHAR2 (30),
6 spcl_char_count NUMBER
7 );
Table created.
PL/SQL code:
SQL> set timing on
SQL>
SQL> DECLARE
2 TYPE t_list_rec IS RECORD
3 (
4 owner all_tab_columns.owner%TYPE,
5 table_name all_tab_columns.table_name%TYPE,
6 column_name all_tab_columns.column_name%TYPE
7 );
8
9 TYPE t_list IS TABLE OF t_list_rec;
10
11 v_array t_list;
12 --
13 l_cnt NUMBER;
14 BEGIN
15 EXECUTE IMMEDIATE 'truncate table subs_profile_spcl_char';
16
17 SELECT owner, table_name, column_name
18 BULK COLLECT INTO v_array
19 FROM all_tab_columns
20 WHERE owner NOT LIKE '%SYS%'
21 AND owner NOT LIKE 'APEX%'
22 AND data_type LIKE '%CHAR%';
23
24 FOR i IN 1 .. v_array.COUNT ()
25 LOOP
26 BEGIN
27 EXECUTE IMMEDIATE 'select count(*) from '
28 || v_array (i).owner
29 || '.'
30 || v_array (i).table_name
31 || ' where not regexp_like ('
32 || v_array (i).column_name
33 || ',''^[A-za-z0-9.]+$'')'
34 INTO l_cnt;
35 EXCEPTION
36 WHEN OTHERS
37 THEN
38 -- for tables you can't access
39 NULL;
40 END;
41
42 IF l_cnt > 0
43 THEN
44 INSERT INTO subs_profile_spcl_char
45 VALUES (v_array (i).owner,
46 v_array (i).table_name,
47 v_array (i).column_name,
48 l_cnt);
49 END IF;
50 END LOOP;
51 END;
52 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.58
SQL> set timing off
It took 580 miliseconds (as opposed to your 1000 seconds).
Number of scanned columns:
SQL> SELECT COUNT (*)
2 FROM all_tab_columns
3 WHERE owner NOT LIKE '%SYS%'
4 AND owner NOT LIKE 'APEX%'
5 AND data_type LIKE '%CHAR%';
COUNT(*)
----------
172
Without filter:
SQL> SELECT COUNT (*)
2 FROM all_tab_columns;
COUNT(*)
----------
39697
SQL>
Why would you check 40.000 columns, if you can do that on 200 columns instead?
Final result:
SQL> SELECT *
2 FROM subs_profile_spcl_char
3 ORDER BY owner DESC, table_name, column_name;
OWNER TABLE_NAME COLUMN_NAME SPCL_CHAR_COUNT
-------------------- ------------------------- -------------------- ---------------
XDB PATH_VIEW PATH 137
XDB RESOURCE_VIEW ANY_PATH 137
SCOTT BONUS JOB 1
SCOTT DEPT LOC 1
SCOTT PRODUCTS PRODUCT_NAME 4
SCOTT SUBS_PROFILE_SPCL_CHAR COLUMN_NAME 18
SCOTT SUBS_PROFILE_SPCL_CHAR OWNER 2
SCOTT SUBS_PROFILE_SPCL_CHAR TABLE_NAME 24
ORDS_METADATA USER_ORDS_OBJECTS OBJECT_ALIAS 3
ORDS_METADATA USER_ORDS_OBJECTS PARSING_OBJECT 3
ORDDATA ORDDCM_DBRELEASE_DOCS DOC_TYPE 3
ORDDATA ORDDCM_DOCUMENT_TYPES DOC_TYPE 4
<snip>
29 rows selected.
SQL>

Oracle View or Table Function that returns union of queries stored as text in another table

Let's say that I have a "Rules" table that has a column that contains oracle queries in a varchar2 column:
Row
Query
1
select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < *some date math goes here*
2
select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < *some date math goes here*
If this were never going to change, I'd just make a view with a union of these queries.
Our requirement is that we be able to add to or to modify these rules on-the-fly at the whims of leadership.
So, what I need is either:
a very smart view (I think impossible) that executes and unions all of these stored query strings
or
a table function that returns the results of the union of these stored query strings. (I think this is the more likely solution)
It will only ever be those two columns: The hardcoded name of the table and the ID of the record.
Can someone help get me started on this?
Thanks
You can use a PIPELINED function.
First create the types:
CREATE TYPE request_data IS OBJECT (tablename VARCHAR2(30), request_id NUMBER);
CREATE TYPE request_list IS TABLE OF request_data;
Then the function:
CREATE FUNCTION get_requests RETURN request_list PIPELINED
IS
BEGIN
FOR r IN (SELECT "QUERY" FROM table_name ORDER BY "ROW")
LOOP
DECLARE
c_cursor SYS_REFCURSOR;
v_tablename VARCHAR2(30);
v_request_id NUMBER;
BEGIN
OPEN c_cursor FOR r."QUERY";
LOOP
FETCH c_cursor INTO v_tablename, v_request_id;
EXIT WHEN c_cursor%NOTFOUND;
PIPE ROW (request_data(v_tablename, v_request_id));
END LOOP;
CLOSE c_cursor;
EXCEPTION
WHEN NO_DATA_NEEDED THEN
CLOSE c_cursor;
RETURN;
END;
END LOOP;
END;
/
Then, if you have the sample data:
CREATE TABLE table_name ("ROW", "QUERY") AS
SELECT 1, q'[select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < SYSDATE]' FROM DUAL UNION ALL
SELECT 2, q'[select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < SYSDATE]' FROM DUAL
CREATE TABLE table_1 (request_id, status, resolve_date) AS
SELECT 42, 4, SYSDATE - 1 FROM DUAL;
CREATE TABLE table_2 (request_id, parent_id) AS
SELECT 57, 42 FROM DUAL;
Then you can use the function in a table collection expression:
SELECT *
FROM TABLE(get_requests());
Which outputs:
TABLENAME
REQUEST_ID
Hardcoded_Tablename_1
42
Table_2
57
db<>fiddle here
One option might be a function that returns refcursor.
SQL> select * from rules;
CROW QUERY
---------- ----------------------------------------------------------------------------------------------------
1 select 'EMP' tablename, empno from emp where hiredate = (select max(hiredate) from emp)
2 select 'DEPT' tablename, d.deptno from emp e join dept d on d.deptno = e.deptno where e.hiredate = (
select min(hiredate) from emp)
Function creates union of all queries from the rules table and uses it as a source for the refcursor:
SQL> create or replace function f_test return sys_refcursor
2 is
3 l_rc sys_refcursor;
4 l_str clob;
5 begin
6 for cur_r in (select query from rules order by crow) loop
7 l_str := l_str || cur_r.query ||' union all ';
8 end loop;
9 l_str := rtrim(l_str, ' union all ');
10
11 open l_rc for l_str;
12 return l_rc;
13 end;
14 /
Function created.
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
TABL EMPNO
---- ----------
EMP 7876
DEPT 20
SQL>

How to fetch odd columns in oracle

How to fetch odd columns in Oracle using a query when number of columns and name of columns are not known?
E.g.:
I need to get output in below format
Column1 column3 column5 column7
And so on....
You need to use the dynamic queries in the procedure as follows:
SQL> CREATE OR REPLACE PROCEDURE ODD_COLUMNS (
2 TABLE_NAME_P IN VARCHAR2,
3 DATAA OUT SYS_REFCURSOR
4 ) AS
5 V_SQL VARCHAR2(4000);
6 BEGIN
7 SELECT
8 'SELECT '
9 ||
10 LISTAGG(COLUMN_NAME, ',') WITHIN GROUP(
11 ORDER BY
12 COLUMN_ID
13 )
14 || ' FROM "'
15 || TABLE_NAME_P
16 || '"'
17 INTO V_SQL
18 FROM
19 USER_TAB_COLS
20 WHERE
21 TABLE_NAME = TABLE_NAME_P
22 AND MOD(COLUMN_ID, 2) = 1;
23
24 OPEN DATAA FOR V_SQL;
25
26 END ODD_COLUMNS;
27 /
Procedure created.
SQL>
Now, Let's test it:
SQL> variable rc refcursor;
SQL> exec ODD_COLUMNS('EMP',:rc);
PL/SQL procedure successfully completed.
SQL> print rc;
EMP_ID E
---------- -
10 N
20 Y
SQL>
SQL> exec ODD_COLUMNS('MY_TABLE1',:rc);
PL/SQL procedure successfully completed.
SQL> print rc;
ID REQ_QTY
---------- ----------
1001 10
1001 20
1001 30
1002 40
1003 10
1003 20
6 rows selected.
SQL>
Cheers!!
This cannot be done simply, but it is possible using the Oracle data dictionary and some dynamic SQL.
To find out the odd-numbered columns you need to look at the ALL_TAB_COLUMNS view. Column COLUMN_ID sequences the columns 1,2,3. So this will find all the odd-numbered columns in the SCOTT.EMP table:
select column_name, column_id
from all_tab_columns
where owner = 'SCOTT'
and table_name = 'EMP'
and mod(column_id,2) = 1
order by column_id;
This will return something like:
COLUMN_NAME COLUMN_ID
----------- ---------
EMPNO 1
JOB 3
HIREDATE 5
COMM 7
We can use the LISTAGG function to make that into a comma-separated list:
select listagg(column_name,',') within group (order by column_id) as result
from user_tab_columns
where table_name = 'EMP'
and mod(column_id,2) = 1;
RESULT
------
EMPNO,JOB,HIREDATE,COMM
Now we can add to that SQL to generate the select statement you want:
select 'select ' || listagg(column_name,',') within group (order by column_id) || ' from ' || table_name as sql
from user_tab_columns
where table_name = 'EMP'
and mod(column_id,2) = 1
group by table_name;
SQL
---
select EMPNO,JOB,HIREDATE,COMM from EMP
(Note I had to add a group by clause because table_name is not being aggregated by LISTAGG).
You could use that SQL within some PL/SQL code to populate a variable v_sql, then use the DBMS_SQL package to run it. But that is a complex topic in itself and I won't go into it here.

Finding max value of a field available in all tables

Example scenario:
5 tables are there and one common field among them is com_field (DATE data type). Now, I need to find the maximum of com_field in each of the five tables. Can someone give the logic?
I know UNION could be used but I need the flexibility not to miss any new table added to the OWNER.
The result I am expecting is like the below.
Table Max(com_field)
Tbl1 10/21/2019
Tbl2 10/18/2019
Tbl3 10/28/2019
Tbl4 09/30/2019
Tbl5 09/09/2019
Run this query:
SELECT
'SELECT '''||OWNER||'.'||TABLE_NAME ||''' AS TABLE_NAME , '||'MAX(COM_FIELD)AS COM_FIELD FROM ' ||OWNER||'.'||TABLE_NAME ||' UNION ALL'
FROM ALL_TAB_COLUMNS
WHERE COLUMN_NAME ='COM_FIELD'
then copy the outpu and delete last union all keyword. Then run the sql statement.
You can order it and see the max value
One option is to use dynamic SQL in a function that returns refcursor. Here's an example.
First, test case:
SQL> create table taba (com_field date);
Table created.
SQL> create table tabb (com_field date);
Table created.
SQL> create table tabc (com_field date);
Table created.
SQL> insert all
2 into taba values (sysdate)
3 into taba values (sysdate - 2)
4 into taba values (sysdate - 3)
5 into tabb values (sysdate + 2)
6 into tabc values (sysdate + 4)
7 into tabc values (sysdate + 5)
8 select * From dual;
6 rows created.
SQL>
Function:
SQL> create or replace function f_maxcom
2 return sys_refcursor
3 is
4 l_str varchar2(1000);
5 rc sys_refcursor;
6 begin
7 for cur_r in (select table_name
8 from user_tab_columns
9 where column_name = 'COM_FIELD'
10 )
11 loop
12 l_str := l_str ||
13 'select ' || chr(39) || cur_r.table_name || chr(39) || ', ' ||
14 'max(com_field) from ' || cur_r.table_name || ' union all ';
15 end loop;
16
17 l_str := rtrim(l_str, ' union all');
18
19 open rc for l_str;
20 return rc;
21 end;
22 /
Function created.
SQL>
Let's try it:
SQL> select f_maxcom from dual;
F_MAXCOM
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
'TAB MAX(COM_FI
---- ----------
TABA 29.10.2019
TABB 31.10.2019
TABC 03.11.2019
SQL>
Add another table to see what happens; function will stay as is:
SQL> create table littlefoot (id number, com_field date);
Table created.
SQL> insert into littlefoot values (100, sysdate);
1 row created.
SQL> select f_maxcom from dual;
F_MAXCOM
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
'LITTLEFOO MAX(COM_FI
---------- ----------
LITTLEFOOT 29.10.2019
TABA 29.10.2019
TABB 31.10.2019
TABC 03.11.2019
SQL>
Seems to be OK, eh?

Getting Ranges from a series of numbers from a table and storing all ranges in a string variable in PLSQL/Oracle Forms

I have a table containing a series of numbers 1,2,3,4,5,11,12,13,14,15,101,102,103,104,105,510,511,512,513,515,516,517.
I want an PL/SQL Function so that I can get the ranges and then store all all the ranges in a single string variable in the following format.
"1-5, 11-15, 101-105, 510-517".
I have get a code to generate the rages in SQL*Plus, but it did not work in PL/SQL and forms. The procedure is given below:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 v_list VARCHAR2(100);
3 BEGIN
4 SELECT listagg(RANGE, ',') WITHIN GROUP(
5 ORDER BY min_num)
6 INTO v_list
7 FROM
8 (SELECT MIN(num) min_num,
9 MIN(num)
10 ||'-'
11 || MAX(num) range
12 FROM
13 (SELECT num, num-Row_Number() over(order by num) AS rn FROM t
14 )
15 GROUP BY rn
16 );
17 dbms_output.put_line(v_list);
18 END;
19 /
1-3,5-7,10-12,20-20
As mentioned here, since the output is a list of strings, you could declare and store the output in a varchar2 variable.
You could create a procedure and put the entire logic in it.
For example,
Setup
SQL> CREATE TABLE t AS
2 SELECT *
3 FROM
4 ( WITH data(num) AS
5 ( SELECT 1 FROM dual
6 UNION
7 SELECT 2 FROM dual
8 UNION
9 SELECT 3 FROM dual
10 UNION
11 SELECT 5 FROM dual
12 UNION
13 SELECT 6 FROM dual
14 UNION
15 SELECT 7 FROM dual
16 UNION
17 SELECT 10 FROM dual
18 UNION
19 SELECT 11 FROM dual
20 UNION
21 SELECT 12 FROM dual
22 UNION
23 SELECT 20 FROM dual
24 )
25 SELECT * FROM DATA);
Table created.
Procedure
SQL> CREATE OR REPLACE
2 PROCEDURE p_get_list
3 AS
4 v_list VARCHAR2(100);
5 BEGIN
6 SELECT listagg(RANGE, ',') WITHIN GROUP(
7 ORDER BY min_num)
8 INTO v_list
9 FROM
10 (SELECT MIN(num) min_num,
11 MIN(num)
12 ||'-'
13 || MAX(num) range
14 FROM
15 (SELECT num, num-Row_Number() over(order by num) AS rn FROM t
16 )
17 GROUP BY rn
18 );
19 dbms_output.put_line(v_list);
20 END;
21 /
Procedure created.
Test case
SQL> SET SERVEROUTPUT ON
SQL> BEGIN
2 p_get_list;
3 END;
4 /
1-3,5-7,10-12,20-20
PL/SQL procedure successfully completed.
You could simply call the procedure in your Oracle Forms.

Resources