Oracle DB SQL Procedure Iterate over table with dynamic table bame - sql

I'm working on an Oracle Stored procedure.
I need to iterate over rows of a table . I can do that using:
FOR eachrow IN table_name
LOOP
END LOOP;
But i need the table_name to be dynamic.
For example the table names are stored in some other table.
So, i can do FOR loop on that table and inside the loop ,i want to iterate through the rows of the new table.
Please suggest how i can achieve that.
Thanks,
Sash

This is not possible with a FOR eachrow IN table_name LOOP, you have to use a ref cursor instead:
First of all some sample data. These tables have some column names in common, in your inner for loop you can only access these columns, this is what I wrote in my comment above. Result type should be the same in the inner loop.
create table froc_a(id number, name varchar2(10), row_added date);
insert into froc_a values (1, '1', sysdate);
insert into froc_a values (2, '2', sysdate - 2);
insert into froc_a values (4, '4', sysdate - 4);
create table froc_b(id number, name2 varchar2(10), row_added date);
insert into froc_b values (1, 'b1', sysdate);
insert into froc_b values (2, 'b2', sysdate - 2);
insert into froc_b values (4, 'b4', sysdate - 4);
create table froc_c(id number, txt varchar2(10), row_added date);
insert into froc_c values (1, 'c1', sysdate);
insert into froc_c values (2, 'c2', sysdate - 2);
insert into froc_c values (4, 'c4', sysdate - 4);
Here is a first approach how to write it:
declare
TYPE curtype IS REF CURSOR;
l_cursor curtype;
l_param_id number;
l_id number;
l_val varchar2(100);
begin
l_param_id := 1;
-- Loop over your table names
for l_rec in (with tabnames(name) as
(select 'froc_a'
from dual
union all
select 'froc_b'
from dual
union all
select 'froc_c'
from dual)
select * from tabnames) loop
dbms_output.put_line(l_rec.name);
-- Open cursor for current table
open l_cursor for 'select id, row_added from ' || l_rec.name || ' where id = :1'
using l_param_id;
-- Loop over rows of current table
loop
fetch l_cursor
into l_id, l_val;
exit when l_cursor%notfound;
dbms_output.put_line(l_id || ', ' || l_val);
end loop;
end loop;
end;
Output:
froc_a
1, 06-APR-16
froc_b
1, 06-APR-16
froc_c
1, 06-APR-16

Related

Dynamically unpivot table within Oracle

I have a table with several hundred columns that I need to unpivot. All of the columns that need to be unpivoted start with 'SIM_'. I know how to do this statically (example below), but I'd like a dynamic solution - as the number of columns that need be be unpivot is both long and may change over time.
SELECT
*
FROM
(SELECT
ID,
NAME,
SIM_1,
SIM_2,
SIM_3
FROM
SAMPLE_TABLE
) T UNPIVOT(SIM_RESULT FOR SIM IN (SIM_1, SIM_2, SIM_3))
Maybe you could (re)create a view when there is a need to refresh the list of columns. This way you will have the accurate list of columns and a way to show real data. It does the unpivoting of all columns (like SIM_%) in the time of execution.
/* sample table and data
CREATE TABLE AA_TST
(ID NUMBER(6), SIM_A VARCHAR2(20), SIM_B VARCHAR2(20), SIM_C VARCHAR2(20), SIM_D VARCHAR2(20), SIM_E VARCHAR2(20));
INSERT INTO AA_TST VALUES(1, 'A', 'B', 'C', 'D', 'E');
INSERT INTO AA_TST VALUES(2, 'AA', 'BB', 'CC', 'DD', 'EE');
INSERT INTO AA_TST VALUES(3, 'AAA', 'BBB', 'CCC', 'DDD', 'EEE');
*/
Declare
col_list VarChar2(1000);
myViewSQL VarChar2(1000);
Begin
SELECT LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_name)
INTO col_list
FROM all_tab_columns
WHERE table_name = 'AA_TST' And
column_name LIKE 'SIM_%';
--
myViewSQL := 'SELECT VALUE_NAME, VALUE_OF FROM AA_TST UNPIVOT (VALUE_OF FOR VALUE_NAME IN(' || col_list || '))';
execute immediate 'CREATE or replace VIEW AA_TST_VIEW AS ' || myViewSQL;
End;
/
SELECT * FROM AA_TST_VIEW;
--
-- R e s u l t
--
-- anonymous block completed
-- VALUE_NAME VALUE_OF
-- ---------- --------------------
-- SIM_A A
-- SIM_B B
-- SIM_C C
-- SIM_D D
-- SIM_E E
-- SIM_A AA
-- SIM_B BB
-- SIM_C CC
-- SIM_D DD
-- SIM_E EE
-- SIM_A AAA
-- SIM_B BBB
-- SIM_C CCC
-- SIM_D DDD
-- SIM_E EEE
--
-- 15 rows selected

Is there any possibility of creating view or table based on the output of a dynamically opened ref cursor?

I have learnt how to return a dynamically opened ref cursor. Now based on the output, I want to create a table or a view.
Input table:
create table sales
(s_sale_name varchar2(20),
s_date_sal date,
s_qty number(10)
);
Records
insert into sales values ('Norb','10-MAR-2019',10000);
insert into sales values ('Bert','10-MAR-2019',5000);
insert into sales values ('Alba','10-MAR-2019',4000);
insert into sales values ('Rob','10-MAR-2019',200000);
insert into sales values ('Norb','11-MAR-2019',5000);
insert into sales values ('Bert','11-MAR-2019',13000);
insert into sales values ('Rob','11-MAR-2019',80000);
insert into sales values ('Norb','12-MAR-2019',1000);
insert into sales values ('Bert','12-MAR-2019',4000);
insert into sales values ('Rob','12-MAR-2019',40000);
insert into sales values ('Alba','12-MAR-2019',2000);
Query output
sales_name 10-MAR-2019 11-MAR-2019 12-MAR-2019
Norb 10000 5000 1000
Bert 5000 13000 4000
Alba 4000 0 2000
Rob 200000 80000 40000
Now the result should be saved in a table or a view. I have learnt how to return a dynamically opened ref cursor so far.
------Here is the procedure I used -----------
create or replace package p_sales_pkg
as
type rc is ref cursor;
procedure get_query( p_cursor in out rc, p_start date, p_end date );
end;
/
create or replace package body p_sales_pkg
as
procedure get_query( p_cursor in out rc, p_start date, p_end date )
is
l_query long := 'select s_name ';
begin
for i in 1 .. trunc(p_end)-trunc(p_start)+1
loop
l_query := l_query || ', sum( decode( trunc(s_date), ' ||
'to_date( ''' || to_char(p_start+i-1,'yyyymmdd') ||
''', ''yyyymmdd'' ), s_qty, 0 )) "' ||
to_char(p_start+i-1) || '"';
end loop;
l_query := l_query || ' from sales group by s_name';
open p_cursor for l_query;
end;
end;
/
set autoprint on
var x refcursor
exec nw_demo_pkg.get_query( :x, '10-MAR-19', '13-MAR-19' );
This is really a very nice and challenging question. I disagree on the #APC point on SELECT part of a CREATE TABLE ... AS SELECT statement. Well we definitely can't do that. What i believe is to every problem in Oracle, there exists a solution.
You requirement can be achieved using a NESTED TABLE. See below:
Set Up:
create table sales
(s_sale_name varchar2(20),
s_date_sal date,
s_qty number(10)
);
/
insert into sales values ('Norb','10-MAR-2019',10000);
insert into sales values ('Bert','10-MAR-2019',5000);
insert into sales values ('Alba','10-MAR-2019',4000);
insert into sales values ('Rob','10-MAR-2019',200000);
insert into sales values ('Norb','11-MAR-2019',5000);
insert into sales values ('Bert','11-MAR-2019',13000);
insert into sales values ('Rob','11-MAR-2019',80000);
insert into sales values ('Norb','12-MAR-2019',1000);
insert into sales values ('Bert','12-MAR-2019',4000);
insert into sales values ('Rob','12-MAR-2019',40000);
insert into sales values ('Alba','12-MAR-2019',2000);
---Created an Object of Sales table to hold intermediate result
create or replace type sales_obj is OBJECT
(obj_sale_name varchar2(20),
obj_date_sal date,
obj_qty number(10)
);
/
-- Table of Sales Object.
create or replace type vtest1Tab is table of sales_obj;
/
Anonymous Block to Create table ccc:
DECLARE
VAR VTEST1TAB ;
vsql varchar2(500);
BEGIN
vsql := 'create table ccc(col1) NESTED TABLE COL1 STORE AS TAB1
as
Select cast(multiset(Select * from SALES) as VTEST1TAB )
from dual
';
Execute immediate vsql ;
END;
Output:
SQL> Select p.*
from ccc c,
table(c.COL1) p ;
In this link , The reply by "Zlatko Sirotic" covers exactly how to identify columns of the cursor and print them.
Look for "dyn_fetch", as the package is generic enough, it can work with any query for printing data. You can use the same approach to insert the data into a table that is created dynamically.

Oracle SQL random value from string set

In Oracle SQL 11g I am trying to fill a table with procedure. For some columns I need to take data randomly from predefined set of strings. How do I define such set and take data from it by random order?
You could use a cte and dbms_random.value. Something like:
with strings as (
select 'string1' as s from dual union all
select 'string2' as s from dual union all
select 'string3' as s from dual union all
select 'string4' as s from dual
)
select <col1>,
(select s
from (select s from strings order by dbms_random.value) s
where rownum = 1
) as RandomString
from dual;
Can you give this a try,it is working.
1.Insert the list of strings in a table (strings).
2.Create a function(RANDOM) to generate random number.
3.Create a procedure(PROC_STRING) to pick a string name from (STRINGS) table using the random number generated from function(RANDOM) and then insert into (NEW_TABLE)
PROGRAM:
--Table with list of string names
Create table strings (string_id number,string_name varchar2(2000) );
--Table to store new string names in random order
Create table new_table (string_id number,string_name varchar2(2000) );
--Function to generate random numbers
create or replace function random(p_number in number)
return number
is
a number;
begin
select dbms_random.value(1,10) into a
from dual;
a := floor(a);
return a;
end;
/
delete from strings;
delete from new_table;
insert into strings values(1,'abc');
insert into strings values(2,'def');
insert into strings values(3,'ghi');
insert into strings values(4,'abc 1');
insert into strings values(5,'def 1');
insert into strings values(6,'ghi 1');
insert into strings values(7,'abc 2');
insert into strings values(8,'def 2');
insert into strings values(9,'ghi 2');
insert into strings values(10,'xyz 3');
--Procedure to pick string names randomly from strings table and insert into new_table
create or replace procedure proc_string(p_no in number)
as
s_id number;
s_name varchar2(2000);
begin
select random(1) into s_id from dual;
select string_name into s_name from strings where string_id = s_id;
insert into New_table values(s_id,s_name);
dbms_output.put_line('insert successfully completed');
commit;
Exception when others
then dbms_output.put_line('ERROR:' || SQLCODE || ' ' || SQLERRM);
end;
/
commit;
EXECUTION:
--After executing the procedure for 3 times
SQL> exec proc_string(1);
insert successfully completed
PL/SQL procedure successfully completed.
-- Random string names got inserted into newtable
SQL> select * from new_table;
STRING_ID STRING_NAME
5 def 1
3 ghi
1 abc
Let me know if you questions.

Follow up on earlier today: Is there any way I can speed up the following insert(s)?

Sorry to bother again but I need an answer to this question since I can't seem to come up with one myself.
Here's the previous post: Is there any way I can speed up the following insert(s)?.
Consider the following:
CREATE TABLE myTable
(
random_value1 NUMBER,
random_value2 NUMBER,
random_string VARCHAR2(5)
);
DECLARE
TYPE arrayType IS VARRAY(5) OF VARCHAR2(5);
v_my_array arrayType := arrayType('foo', 'bar', 'baz', 'qux', 'quux');
max NUMBER := 1000000;
BEGIN
FOR j IN 1..max
LOOP
INSERT INTO myTable VALUES(DBMS_RANDOM.VALUE(1, 500),
DBMS_RANDOM.VALUE(1, 500), v_my_array(DBMS_RANDOM.VALUE(1, 5)));
END LOOP;
END;
/
Based on the answers I got on the previous post I can easily insert random values on the columns #1 and #2 using a single insert statement instead of a million(see the example). Now my question is how can I also insert a random string from a list of given strings and avoid using a loop, if that's possible of course.
If I try something like:
INSERT INTO myTable
SELECT
DBMS_RANDOM.VALUE(1, 500),
DBMS_RANDOM.VALUE(1, 500),
v_my_array(DBMS_RANDOM.VALUE(1, 5))
FROM DUAL
CONNECT BY
LEVEL <= 1000000;
I get the same value for column #3 on all the rows, when I want different results everytime("random" results).
Once again, thanks for taking the time to look over this!
What about this approach to put strings into subquery
INSERT INTO myTable
SELECT
DBMS_RANDOM.VALUE(1, 500),
DBMS_RANDOM.VALUE(1, 500),
myvarchar.a
FROM
(select a from
(
SELECT 'foo' a from dual
union
select 'bar' a from dual
union
select 'baz' a from dual
union
select 'qux' a from dual
union
select 'quux' a from dual
)
ORDER BY
DBMS_RANDOM.RANDOM) myvarchar
CONNECT BY
LEVEL <= 10;
/
This select statement returns over 12 mil rows, so scale appropriately.
I run it for LEVEl<=7 and 97k rows. On my machine it took 3 seconds.
Something like this:
DECLARE
TYPE arrayType IS VARRAY(5) OF VARCHAR2(5);
v_my_array arrayType := arrayType('foo', 'bar', 'baz', 'qux', 'quux');
max NUMBER := 1000000;
TYPE t_data IS TABLE OF myTable%ROWTYPE INDEX BY PLS_INTEGER;
v_data t_data;
v_row myTable%ROWTYPE;
BEGIN
FOR i IN 1..10
LOOP
FOR j IN 1..100000
LOOP
v_row.random_value1 := DBMS_RANDOM.VALUE(1, 500);
v_row.random_value2 := DBMS_RANDOM.VALUE(1, 500);
v_row.random_string := v_my_array(DBMS_RANDOM.VALUE(1, 5));
v_data(j) := v_row;
END LOOP;
FORALL k IN INDICIES OF v_data
INSERT INTO myTable VALUES v_Data(k);
END LOOP;
END;
/

Oracle Database - Dynamic number of columns

I have the following 3 Oracle Database Tables:
application (app_id, application_name)
eg. (1, firstapp)
item_request (app_id, item_id, qty_requested)
eg. (1, 111, 5), (1, 112, 3), (1, 113, 7)
item (item_id, item_code)
eg. (111, "COMPUTER"), (112, "PHONE"), (113, "DESK")
I want to produce this table:
new table (app_id,
application_name,
qty_requested_for_item_with_item_code_111,
qty_requested_for_item_with_item_code_112,
qty_requested_for_item_with_item_code_113,
etc...)
eg. (1, fistapp, 5, 3, 7, etc...)
Is this even possible?
You can achieve this using dynamic SQL, e.g. with a ref cursor. You have to
a)iterate over all existing items
b)build your SELECT list by adding all items
c)perform the pivoting (either by using PIVOT or with the traditional MAX / CASE / GROUP BY) approach
create table application(app_id number primary key, application_name varchar2(30));
create table item_request(app_id number, item_id number, qty_requested number);
create table item(item_id number primary key, item_code varchar2(30));
insert into application values(1, 'firstapp');
insert into application values(2, 'secondapp');
insert into item values (111, 'Computer');
insert into item values (112, 'Phone');
insert into item values (113, 'Desk');
insert into item_request values (1, 111, 5);
insert into item_request values (1, 112, 3);
insert into item_request values (1, 113, 7);
insert into item_request values (2, 111, 3);
-- SQL/Plus syntax for declaring and using a bind variable of type ref cursor
var x refcursor;
set autoprint on
declare
l_sql varchar2(4000);
l_select varchar2(4000);
l_from varchar2(4000);
begin
l_select := 'select application.app_id, application.application_name';
for cur in (select * from item)
loop
l_select := l_select || chr(10) || ',max(case when item_code = ''' || cur.item_code || ''' then qty_requested else 0 end) as ' || cur.item_code;
end loop;
l_sql := l_select || '
from application
left join item_request on application.app_id = item_request.app_id
left join item on item.item_id = item_request.item_id
group by application.app_id, application.application_name';
dbms_output.put_line(l_sql);
open :x for l_sql;
end;
Oracle has limited support for Pivot functions.
See e.g. http://www.oracle.com/technetwork/articles/sql/11g-pivot-097235.html.
In short, for regular output you are stuck with having a static list of columns that you are pivoting into.
For dynamic pivot tables you can use the pivot xml function. But this produces the pivot columns in xml, and so will need parsing.
You can application and item request based on app_id and do a pivot based on itemid and get quantity.
select * from (select app_id, item_id, qty from application a, item_request ir
where a.app_id = ir.app_id) pivot(sum(qty) for item_id in (Item List)).
If the item_id list is complete item_id list use a select query in place of item list.