How to return result of many select statements as one custom table - sql

I have a table (let's name it source_tab) where I store list of all database tables that meet some criteria.
tab_name: description:
table1 some_desc1
table2 some_desc2
Now I need to execute a select statement on each of these tables and return a result as a table (I created custom TYPE). However I have a problem - when using bulk collect, only the last select statement is returned. The same issue was with open cursor. Is there any possibility to achieve this goal, another then concatenating all select statements using union all and executing it as one statement? And because I'm the begginer in sql, my second question is, is it ok to use this dynamic sql in terms of sql injection issues? Below is simplified version of my code:
CREATE OR REPLACE FUNCTION my_function RETURN newly_created_table_type IS
ret_tab_type newly_created_table_type;
BEGIN
for r in (select * from source_tab)
loop
execute immediate 'select value1, value2,''' || r.tab_name || ''' from ' || r.tab_name bulk collect into ret_tab_type;
end loop;
return ret_tab_type;
END;
I'm using Oracle 11.

In your case you are trying to populate a collection dynamically and wanted result in a single collection. In your case its not possible to do that in a single loop. Also as mentioned by #OldProgrammer, piperow would be a better solution from performance point. See below demo:
--Tables and Values:
CREATE TABLE SOURCE_TAB(TAB_NAME VARCHAR2(100), DESCRIPTION VARCHAR2(100));
/
SELECT * FROM SOURCE_TAB;
/
INSERT INTO SOURCE_TAB VALUES('table1','some_desc1');
INSERT INTO SOURCE_TAB VALUES('table2','some_desc2');
/
CREATE TABLE TABLE1(COL1 NUMBER, COL2 NUMBER);
/
INSERT INTO TABLE1 VALUES(1,2);
INSERT INTO TABLE1 VALUES(3,4);
INSERT INTO TABLE1 VALUES(5,6);
/
Select * from TABLE1;
/
CREATE TABLE TABLE2(COL1 NUMBER, COL2 NUMBER);
/
INSERT INTO TABLE2 VALUES(7,8);
INSERT INTO TABLE2 VALUES(9,10);
INSERT INTO TABLE2 VALUES(11,12);
/
Select * from TABLE2;
/
--Object Created
--UDT
CREATE OR REPLACE TYPE NEWLY_CREATED_TABLE_TYPE IS OBJECT (
VALUE1 NUMBER,
VALUE2 NUMBER
);
/
--Type of UDT
CREATE OR TYPE NEWLY_CRTD_TYP AS TABLE OF NEWLY_CREATED_TABLE_TYPE;
/
--Function:
--Function
CREATE OR REPLACE FUNCTION MY_FUNCTION
RETURN NEWLY_CRTD_TYP PIPELINED
AS
CURSOR CUR_TAB
IS
SELECT *
FROM SOURCE_TAB;
RET_TAB_TYPE NEWLY_CRTD_TYP;
BEGIN
FOR I IN CUR_TAB
LOOP
--Here i made sure that all the tables have col1 & col2 columns since you are using dynamic sql.
EXECUTE IMMEDIATE 'select NEWLY_CREATED_TABLE_TYPE(COL1, COL2) from '|| I.TAB_NAME
BULK COLLECT INTO RET_TAB_TYPE;
EXIT WHEN CUR_TAB%NOTFOUND;
FOR REC IN 1 .. RET_TAB_TYPE.COUNT
LOOP
PIPE ROW (RET_TAB_TYPE (REC) );
END LOOP;
END LOOP;
RETURN;
END;
/
Output:
SQL> Select * from table(MY_FUNCTION);
VALUE1 VALUE2
---------- ----------
1 2
3 4
5 6
7 8
9 10
11 12
6 rows selected.

May be you can combine all the queries into one using UNION ALL before execution, if the number and type of columns to be retrieved from all the tables are identical.
CREATE OR REPLACE FUNCTION my_function
RETURN newly_created_table_type
IS
ret_tab_type newly_created_table_type;
v_query VARCHAR2 (4000);
BEGIN
SELECT LISTAGG (' select VALUE1,VALUE2 FROM ' || tab_name, ' UNION ALL ')
WITHIN GROUP (ORDER BY tab_name)
INTO v_query
FROM source_tab;
EXECUTE IMMEDIATE v_query BULK COLLECT INTO ret_tab_type;
RETURN ret_tab_type;
END;
You could then use a single select statement to get all the values.
select * FROM TABLE ( my_function );

Related

Trying to query a redshift within SELECT statement

Current table1:
col1
-------------
schema.table1
schema.table2
schema.table3
Desired table1:
col1 col2
------------------------------------------------------------
schema.table1 value of (select count(*) from schema.table1)
schema.table2 value of (select count(*) from schema.table1)
schema.table3 value of (select count(*) from schema.table1)
It is not working, I tried using function too, but function doesn't allow to use 'FROM'
select col1, (select count(*) from col1)
from table1
I am trying to create this query in redshift. Can anyone please help me out?
To perform this task you will need a stored procedure AND a defined cursor. The stored procedure allows for looping and the cursor provides the ability to execute a newly created statement (dynamic querying).
For example:
Create the starting materials, 3 tables and a table that references these tables.
create table foo as (select 1 as A);
create table goo as (select 2 as A);
create table hoo as (select 3 as A);
create table tabs as (select 'foo' as tab union all select 'goo' union all select 'hoo');
Next define the stored procedure the will create the dynamic SQL
CREATE OR REPLACE procedure count_tabs(curs1 INOUT refcursor)
AS
$$
DECLARE
row record;
statement varchar := '';
union_needed BOOL := false;
BEGIN
for row in select tab from tabs LOOP
IF union_needed THEN
statement := statement || ' UNION ALL ';
END IF;
statement := statement || 'select \'' || row.tab || '\' as table_name, count(*) as table_count from ' || row.tab ;
union_needed := true;
END LOOP;
RAISE NOTICE 'sql to execute: %',statement;
open curs1 for execute statement;
END;
$$ LANGUAGE plpgsql;
Lastly we need to call the procedure and execute the cursor
call count_tabs('mycursor');
fetch 1000 from mycursor;
A few notes on this:
This assumes you want the results as output on your bench. If you want to create a table with the results this is doable in the same structure
Since the FROM clause value(s) is unknown at compile time this needs to be done in 2 steps - create the query and then execute the query.
I believe you can have the procedure walk this same cursor itself but doing this is exceptionally slow

Calculate values from column having expression

I have a table "test_calculate" this has a column "CONN_BY" having values
column can have more than 2 number to multiply and this table may contain millions of rows , I need to get the result of the calculation from "CONN_BY" to "MVP".
I have used xmlquery for the calculation and dynamic query but these are quite slow. Is there another way which is much faster .Please suggest.
You can try the dynamic query.
Create a function which returns the calculated value and use it in your insert or select queries.
CREATE OR REPLACE FUNCTION UFN_CALCULATE (CLM_VALUE VARCHAR2)
RETURN NUMBER IS
RES_VAL NUMBER;
BEGIN
EXECUTE IMMEDIATE 'select '||CLM_VALUE||' FROM DUAL' INTO RES_VAL;
RETURN RES_VAL;
END;
You can use that function like below.
SELECT UFN_CALCULATE('.0876543 * .09876') FROM DUAL;
SELECT UFN_CALCULATE(CONN_BY) FROM YOUR_TABLE;
One option is using select ... connect by level <= regexp_count(conn_by,'[^*]+')... query for the implicit cursor within a PL/SQL code block
SQL> set serveroutput on
SQL> declare
mvp owa.nc_arr; -- numeric array to initialize each multiplication to 1 for each id value
begin
dbms_output.put_line('ID MVP');
dbms_output.put_line('--------');
for c in
(
select id,
to_number( regexp_substr(conn_by,'[^*]+',1,level) ) as nr,
level as lvl , max( level ) over ( partition by id ) as mx_lvl
from test_calculate
connect by level <= regexp_count(conn_by,'[^*]+')
and prior sys_guid() is not null
and prior conn_by = conn_by
order by id, lvl
)
loop
if c.lvl = 1 then mvp(c.id) := 1; end if;
mvp(c.id) := c.nr * mvp(c.id);
if c.lvl = c.mx_lvl then
dbms_output.put_line(c.id||' '||mvp(c.id));
end if;
end loop;
end;
/
where test_calculate is assumed to have an identity column(id)
Demo

How do I write a cursor to return only one column that contains digit values

For example I had a table named car with two column(col1,col2)
For now I would like insert some values inside the column like:
('Super car','Yellow car')
('BMW5','XL')
('Benz','AGM')
so I would like write a cursor to return ('BMW5','XL') in one single column, how do I do that?(I'm using sql developer)
I would appreciate any suggestion! Thank you!
declare
cursor mycursor is select concat(col1,col2) from car where REGEXP_LIKE(left, '^[[:digit:]]+$')
begin
for counter in mycursor
loop
dbms_output.put_line(counter.concat);
endloop;
end
You can use Concat() function in your query inside the cursor i.e.
CONCAT returns Col1 concatenated with Col2
select concat(col1,col2)
from car where <<conditions if any>>
You can write above query in Cursor like
DECLARE
CURSOR car_cursor IS select concat(col1,col2)
from car WHERE REGEXP_LIKE(col1, '[[:digit:]]');
cv_col1_col2 VARCHAR2 ;
BEGIN
OPEN car_cursor;
LOOP
FETCH car_cursor INTO cv_col1_col2;
Dbms_output.put_line('Concated Name' || cv_col1_col2)
END LOOP
CLOSE car_cursor;
END;
This is how I understood the question (though, more through by description than the title as they aren't related much).
Sample data:
SQL> create table car (col1 varchar2(10), col2 varchar2(10));
Table created.
SQL> insert into car
2 select 'Super car', 'Yellow car' from dual union all
3 select 'BMW5', 'XL' from dual union all
4 select 'Benz', 'AGM' from dual;
3 rows created.
PL/SQL code that uses a cursor FOR loop, returning concatenated col1 and col2 values for rows in which either of those columns contains a digit:
SQL> set serveroutput on;
SQL> begin
2 for cur_r in (select col1 ||' '|| col2 result
3 from car
4 where regexp_like(col1 || col2, '\d') -- any column contains
5 ) -- a digit
6 loop
7 dbms_output.put_line(cur_r.result);
8 end loop;
9 end;
10 /
BMW5 XL
PL/SQL procedure successfully completed.
SQL>

Oracle - PLSQL to check the difference in number of records after a truncate/load procedure

Our IT team loads couple of tables every month. The new load should have more records than the previous load, with at least 2% more records.
It's a truncate and load process, I'm collecting the num of records from each table before the truncate, and I'm checking the difference in excel every month to make sure the data load is correct.
Is there anyway to automate this in Oracle.
eg:
Table_name Before_cnt After_cnt
XX_TEST1 4,606,619,326 4,983,759,822
XX_TEST2 121,973,005 123,161,581
You can apply the steps just like below :
SQL> create table XX_TEST1( id int primary key );
SQL> insert into XX_TEST1 select level from dual connect by level <= 100;
SQL> begin -- if table exists, then drop it!
for c in (select table_name from cat where table_name = 'XX_TEST1_OLD' )
loop
execute immediate 'drop table '||c.table_name;
end loop;
end;
/
SQL> create table XX_TEST1_old as select count(*) as cnt from XX_TEST1;
SQL> begin
execute immediate 'truncate table XX_TEST1';
end;
/
SQL> insert into XX_TEST1 select level from dual connect by level <= 103;
SQL> with xt1_new(cnt_new) as
(
select count(id) from XX_TEST1
)
select case when sign( (100 * ( cnt_new - cnt) / cnt)-2 ) = 1 then 1
else 0 end as "Rate Satisfaction"
from XX_TEST1_old
cross join xt1_new;
If this SELECT statement retuns 1, then we're successful to reach the target, else returns 0 and means we're unsuccessful.
Demo

select multiple columns of single row as elements of array

I have a table with 100 columns called like col_1, col_2, .. col_100
is there a way to select values of that columns of single into an array of 100 elements?
(Oracle 10.2)
you could just select them like:
SQL> create type foo as table of number; -- or varray, as you wish.
2 /
Type created.
SQL> select foo(l.a, l.b, l.c) foo from your_tab l;
FOO
-----------------
FOO(1, 2, 3)
etc..
Here's a brute force method. There's probably a more elegant way, or at least one that will cut down on typing. The example uses five columns rather than 100.
DECLARE
-- Change VARCHAR2(10) in the next line to your col_1 .. col_100 type
TYPE My100Array IS TABLE OF VARCHAR2(10) INDEX BY PLS_INTEGER;
myVals My100Array;
indx NUMBER;
BEGIN
SELECT 'These', 'are', 'the', 'column', 'values'
INTO myVals(1), myVals(2), myVals(3), myVals(4), myVals(5)
FROM DUAL;
FOR INDX IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(indx || ': ' || myVals(indx));
END LOOP;
END;
Here's the output when I run this:
1: These
2: are
3: the
4: column
5: values
Of course, this will be a bit tough with 100 columns, but once you get the query out of the way you'll have the array as you want it.
Another example:
DECLARE
CURSOR c_data IS
SELECT * FROM scott.emp; -- replace emp table with your_table
TYPE t_source_tab IS TABLE OF scott.emp%ROWTYPE;
l_tab t_source_tab;
BEGIN
SELECT * BULK COLLECT INTO l_tab FROM scott.emp;
-- display values in array --
FOR i IN l_tab.FIRST ..l_tab.LAST
LOOP
DBMS_OUTPUT.PUT_LINE (l_tab(i).hiredate ||chr(9)||l_tab(i).empno ||chr(9)||l_tab(i).ename);
END LOOP;
END;
/
sounds like you want to unpivot your data..
unfortunately UNPIVOT was only added in 11g (not 10.2)
you could manually unpivot but one of the other solutions would work better i think.
However, if you were on 11g or later you could try this
create table my_table (col1 number,col2 number, col3 number);
Table MY_TABLE created.
insert into my_table values (4,5,6);
1 row inserted.
select * from my_table;
COL1 COL2 COL3
---------- ---------- ----------
4 5 6
select val from my_table unpivot ( val for col in ( col1,col2,col3));
VAL
----------
4
5
6
from there is trivial to select into an single column array
DECLARE
CURSOR c_data IS
select val from my_table unpivot ( val for col in ( col1,col2,col3));
TYPE t_source_tab IS TABLE OF c_data%ROWTYPE;
l_tab t_source_tab;
BEGIN
open c_data;
fetch c_data bulk collect into l_tab;
close c_data;
-- display values in array --
FOR i IN l_tab.FIRST ..l_tab.LAST
LOOP
DBMS_OUTPUT.PUT_LINE (l_tab(i).val);
END LOOP;
END;
/