How to access different columns of a different tables using cursor after a join has been made on a particular column name in the tables - oracle

So I am having issues in understanding how can one access different column names using a cursor in which a join operation has been made on three different tables over a single common column.
For example
DECLARE
CURSOR joined_table_cur IS
SELECT a.col1, a.col2, b.col5, c.col7 ...
FROM table1 a
JOIN table2 b ON a.col1 = b.col1 JOIN
table3 c on b.col1 = c.col1
;
joined_table_rec joined_table_cur%ROWTYPE;
BEGIN
FOR joined_table_rec IN joined_table_cur
LOOP
-- how to access col7 from table3 ie c in this cursor--
END LOOP;
END;
I am unable to understand how to do this.

This is not an answer. d r has answered your question and explained the two options how to access your cursor.
I just want to add that you don't need an explicit cursor at all. What I usually do is just this:
BEGIN
FOR rec IN
(
SELECT a.col1, a.col2, b.col5, c.col7 ...
FROM table1 a
JOIN table2 b ON a.col1 = b.col1
JOIN table3 c ON b.col1 = c.col1
)
LOOP
DBMS_OUTPUT.PUT_LINE('table1.col1 is ' || rec.col1);
DBMS_OUTPUT.PUT_LINE('table2.col5 is ' || rec.col5);
...
END LOOP;
END;
Consider this a comment to dr's answer. I am merely posting this here as an "answer", because without line breaks it would be hard to read as a comment.

To loop through cursor rows in your sample all you should do is to use second type of looping in code below and reference the columns using already declared variable joined_table_rec - for col1 it is joined_table_rec.col1, for col2 joined_table_rec.col2 ... and so on.
If you want to use FOR LOOP then you don't need to declare joined_table_rec variable as the for loop would create the variable itself - just give the name - rec2 in code below.
Below is example for two ways how to loop the cursor:
SET SERVEROUTPUT ON
DECLARE
CURSOR cur IS
SELECT *
FROM
(
Select 1 "COL_ID", 'Name 1' "COL_NAME", 'Somethong else 1' "COL_ELSE" From Dual Union All
Select 2 "COL_ID", 'Name 2' "COL_NAME", 'Somethong else 2' "COL_ELSE" From Dual Union All
Select 3 "COL_ID", 'Name 3' "COL_NAME", 'Somethong else 3' "COL_ELSE" From Dual
);
rec cur%ROWTYPE;
m_sql VarChar2(500);
BEGIN
FOR rec2 IN cur LOOP
DBMS_OUTPUT.PUT_LINE(rec2.COL_ID);
END LOOP;
OPEN cur;
LOOP
FETCH cur Into rec;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(rec.COL_NAME);
END LOOP;
CLOSE cur;
END;
Result:
anonymous block completed
1
2
3
Name 1
Name 2
Name 3
More about it here.

Related

How to get the number of rows in ROWTYPE variable

I have two tables Table1 an dTable2 that have identical columns. I need to check if a particular id is in one of them and return the row of data from whichever table.
I have the following PL/SQL code:
v_result Table1%ROWTYPE;
BEGIN
SELECT a.*
INTO v_result
FROM Table1 a
WHERE a.id = 123;
EXCEPTION
WHEN NO_DATA_FOUND THEN -- when record not found
SELECT b.*
INTO v_result
FROM Table2 b
WHERE b.id = 123;
END;
The issue is that the exception does not get thrown, so v_result returns no data. How can I check v_result for the number of rows?
For cursor I can use ROWCOUNT but v_result is not a cursor.
I also tried using count property but it errored out.
I changed my code to:
v_result Table1%ROWTYPE;
BEGIN
SELECT a.*
INTO v_result
FROM Table1 a
WHERE a.id = 123;
if v_result.count =0 then
SELECT b.*
INTO v_result
FROM Table2 b
WHERE b.id = 123;
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN -- when record not found
SELECT b.*
INTO v_result
FROM Table2 b
WHERE b.id = 123;
END;
And got an error component 'count' must be declared
What am I doing wrong?
You may use only a single row in a record variable. If you want to store and count multiple rows, you may define a collection of records and use BULK COLLECT INTO to load all of them at once and it won't raise a NO_DATA_FOUND. The count function works on collections.
DECLARE
TYPE type_tab1 IS TABLE OF Table1%ROWTYPE;
TYPE type_tab2 IS TABLE OF Table2%ROWTYPE;
v_result1 type_tab1;
v_result2 type_tab2;
BEGIN
SELECT a.*
BULK COLLECT INTO v_result1
FROM Table1 a
WHERE a.id = 123;
if v_result1.count = 0 then
SELECT b.* BULK COLLECT
INTO v_result2
FROM Table2 b
WHERE b.id = 123;
end if;
DBMS_OUTPUT.PUT_LINE('v_result1 ='|| v_result1.count);
DBMS_OUTPUT.PUT_LINE('v_result2 ='|| v_result2.count);
END;
/
Output for the Demo
v_result1 =0
v_result2 =1
If your intention is to simply check if a row exists, then a simpler and efficient approach would be to use EXISTS
SELECT
CASE WHEN
EXISTS (
SELECT 1
FROM table1
WHERE id = 123
) THEN 1
ELSE 0
END
INTO v_count
FROM dual;
IF v_count = 0
THEN
..
..

Oracle view: how to obtain a column definition

I have an Oracle view:
create view schemaName.viewName as
select case when 1=1 then 1 else 2 end as col1, decode('A','A','B','C') as col2 from dual
Is there a way to obtain an output or a table with this information:
Column_Name: Col1
Column_Definition: case when 1=1 then 1 else 2 end
Column_Name: Col2
Column_Definition: decode('A','A','B','C')
Thank you very much
Here is the below anonymous block to get column name and column definition for above view.
DECLARE
a varchar2(1000);
b varchar2(1000);
c varchar2(1000);
BEGIN
EXECUTE IMMEDIATE 'SELECT TEXT FROM user_views
WHERE VIEW_NAME=''TEST_VW'' ' INTO a;
SELECT regexp_replace(regexp_substr(a, '^[^,]*'),'select|as\W','') -- getting first argument case to get "case when 1=1 then 1 else 2 end col1"
into b from dual;
DBMS_OUTPUT.PUT_LINE( 'Column_Name: '|| regexp_substr(b,'[^ ]+$')); -- this command to get last col1 from "case when 1=1 then 1 else 2 end col1"
DBMS_OUTPUT.PUT_LINE('Column_Definition: '|| regexp_replace(b,'[^ ]+$','')); -- this command to make col1 to empty to get column definition
select regexp_replace(substr(a,length(regexp_substr(a, '^[^,]*'))+2),'select|dual|from|as\W','') into c from dual; -- this command to get 2nd column "decode('A','A','B','C') col2"
DBMS_OUTPUT.PUT_LINE( 'Column_Name: '|| REGEXP_SUBSTR ( c , '[^ ]+' , 1 , 2 )); -- This command will get 2nd column col2 from "decode('A','A','B','C') col2"
DBMS_OUTPUT.PUT_LINE( 'Column_Definition: '|| REGEXP_SUBSTR ( c , '[^ ]+' , 1 , 1 )); -- This command will get 1st column decode('A','A','B','C') from "decode('A','A','B','C') col2"
END;
Thanks for clarifying ... just try below block and see if this helps ..Also let me know for any column definition which is incorrect
prerequiste for ananymous block:
create view TEST_VW as
select case when 1=1 then 1 else 2 end as col1, decode('A','A','B','C') as col2,
null as col3 , sysdate as col4 , 'var1' as col5
from dual;
-- In view definition I hope a format of each column as and separator of each columns is key to separate the column name and definition for this block.
create sequence seq
start with 1;
create table col_nm_def
(column_nm varchar2(1000),
column_def varchar2(1000),
id number);
Ananymous block;
DECLARE
a varchar2(1000);
b varchar2(1000);
c varchar2(1000);
d number;
BEGIN
EXECUTE IMMEDIATE 'SELECT TEXT FROM user_views
WHERE VIEW_NAME=''TEST_VW'' ' INTO a;
EXECUTE IMMEDIATE 'delete from col_nm_def';
EXECUTE IMMEDIATE 'drop sequence seq';
EXECUTE IMMEDIATE 'create sequence seq
start with 1';
select regexp_replace(regexp_replace(a,'select|from|dual',''),'as\W|, ' , '|') into b from dual; -- replacing as and (, ) to | symbol (try to change symbol if view uses this pipe in transformation)
select regexp_count(b,'[|]') into d from dual;
d:=d+1;
For i in 1..d
LOOP
insert into col_nm_def values (REGEXP_SUBSTR ( b , '[^|]+' , i+1 , i+1 ),REGEXP_SUBSTR ( b , '[^|]+' , i , i ),seq.nextval);
COMMIT;
END LOOP;
END;
Final result : select only odd rows
select * from col_nm_def where mod(id,2)=1 order by ID;

Execute Immediate Loop PL/SQL

Can I use "execute immediate" with for cycle?
I need of generate a combinations of values in pl/sql.
About this I'm thinking of call a series of nested dynamics for cycle.
Example:
column 1 have this values: A,B
column 2 have this values: C,D,E
I would like to generate this combination:
AC / AD/ AE/ BC / BD /BE
I'm thinking to obtain this like:
for i in 1..count.column1
for j in 1..count.column2
dbms_output.put_line(column1.value(i)||'-'||column1.value(j));
end loop;
end loop;
Due to I don't know the number of column (variable),
Can I use a execute immediate?
declare
sql_stmt varchar2(200);
begin
sql_stmt := 'for i in 1..count.column1 for j in 1..count.column2 dbms_output.put_line(column1.value(i)||'-'||column1.value(j)); end loop; end loop';
execute immediate sql_stmt;
end;
But I have error ORA-06512.
How can i make this? :)
Thank you in advance for your suggestion!
If you really want to do this slowly in PL/SQL using nested loops, you don't need anything to be dynamic. You'd just want
for i in (select distinct column1
from your_table)
loop
for j in (select distinct column2
from your_table)
loop
dbms_output.put_line( i.column1 || j.column2 );
end loop;
end loop;
In the vast majority of cases, though, you'd be better off doing this in SQL
with col1 as (
select distinct col1 val
from your_table
),
col2 as (
select distinct col2 val
from your_table
)
select col1.val || col2.val
from col1
cross join col2
I think you can easily get the combinations using this select statement.
SELECT DISTINCT t1.col1, t2.col2
FROM table_name t1, table_name t2;

How to pass cursor values into variable?

I am trying to read values from two column1, column2 from table1 using cursor. Then I want to pass these values to another cursor or select into statement
so my PL/Sql script will use the values of these two columns to get data from another table called table2
Is this possible? And what's the best and fastest way to do something like that?
Thanks :)
Yes, it's possible to pass cursor values into variables. Just use fetch <cursor_name> into <variable_list> to get one more row from a cursor. After that you can use the variables in where clause of some select into statement. E.g.,
declare
cursor c1 is select col1, col2 from table1;
l_col1 table1.col1%type;
l_col2 table1.col2%type;
l_col3 table2.col3%type;
begin
open c1;
loop
fetch c1 into l_col1, l_col2;
exit when c1%notfound;
select col3
into l_col3
from table2 t
where t.col1 = l_col1 --Assuming there is exactly one row in table2
and t.col2 = l_col2; --satisfying these conditions
end loop;
close c1;
end;
If you use an implicit cursor, then it's even simpler:
declare
l_col3 table2.col3%type;
begin
for i in (select col1, col2 from table1)
loop
select col3
into l_col3
from table2 t
where t.col1 = i.col1 --Assuming there is exactly one row in table2
and t.col2 = i.col2; --satisfying these conditions
end loop;
end;
In these examples, it's more efficient to use a subquery
begin
for i in (select t1.col1
, t1.col2
, (select t2.col3
from table2 t2
where t2.col1 = t1.col1 --Assuming there is atmost one such
and t2.col2 = t1.col2 --row in table2
) col3
from table1 t1)
loop
...
end loop;
end;

refactoring large cursor queries by splitting into multiple cursors

Another PL/SQL refactoring question!
I have several cursors that are of the general simplified form:
cursor_1 is
with X as (select col1, col2 from TAB where col1 = '1'),
Y as (select col1, col2 from TAB where col2 = '3'),
/*main select*/
select count(X.col1), ...
from X inner join Y on...
group by rollup (X.col1, ...
cursor_2 is
with X as (select col1, col2 from TAB where col1 = '7' and col2 = '9' and col3 = 'TEST'),
Y as (select col1, col2 from TAB where col3 = '6'),
/*main select*/
select count(X.col1), ...
from X inner join Y on...
group by rollup (X.col1, ...
cursor_2 is
with X as (select col1, col2 from TAB where col1 IS NULL ),
Y as (select col1, col2 from TAB where col2 IS NOT NULL ),
/*main select*/
select count(X.col1), ...
from X inner join Y on...
group by rollup (X.col1, ...
...
begin
for r in cursor_1 loop
print_report_results(r);
end loop;
for r in cursor_2 loop
print_report_results(r);
end loop;
...
end;
Basically, all of these cursors (there's more than 3) are the same summary/reporting queries. The difference is in the factored subqueries. There are always 2 factored subqueries, "X" and "Y", and they always select the same columns to feed into the main reporting query.
The problem is that the main reporting query is VERY large, about 70 lines. This itself isn't so bad, but it was copy-pasted for ALL of the reporting queries (I think there's over a dozen).
Since the only difference is in the factored subqueries (and they all return the same columns, it's really just a difference in the tables they select from and their conditions) I was hoping to find a way to refactor all this so that there is ONE query for the giant report and smaller ones for the various factored subqueries so that when changes are made to the way the report is done, I only have to do it in one place, not a dozen. Not to mention a much easier-to-navigate (and read) file!
I just don't know how to properly refactor something like this. I was thinking pipelined functions? I'm not sure they're appropriate for this though, or if there's a simpler way...
On the other hand, I also wonder if performance would be significantly worse by splitting out the reporting query. Performance (speed) is an issue for this system. I'd rather not introduce changes for developer convenience if it adds significant execution time.
I guess what I'd ultimately like is something that looks sort of like this (I'm just not sure how to do this so that it will actually compile):
cursor main_report_cursor (in_X, in_Y) is
with X as (select * from in_X),
Y as (select * from in_Y)
/*main select*/
select count(X.col1), ...
from X inner join Y on...
group by rollup (X.col1, ...
cursor x_1 is
select col1, col2 from TAB where col1 = '1';
cursor y_1 is
select col1, col2 from TAB where col2 = '3'
...
begin
for r in main_report_cursor(x_1,y_1) loop
print_report_results(r);
end loop;
for r in main_report_cursor(x_2,y_2) loop
print_report_results(r);
end loop;
...
(Using Oracle 10g)
Use a pipelined function. For example:
drop table my_tab;
create table my_tab
(
col1 number,
col2 varchar2(10),
col3 char(1)
);
insert into my_tab values (1, 'One', 'X');
insert into my_tab values (1, 'One', 'Y');
insert into my_tab values (2, 'Two', 'X');
insert into my_tab values (2, 'Two', 'Y');
insert into my_tab values (3, 'Three', 'X');
insert into my_tab values (4, 'Four', 'Y');
commit;
-- define types
create or replace package refcur_pkg is
--type people_tab is table of people%rowtype;
type my_subquery_tab is table of my_tab%rowtype;
end refcur_pkg;
Create the function pipelined
-- create pipelined function
create or replace function get_tab_data(p_cur_num in number, p_cur_type in char)
return REFCUR_PKG.my_subquery_tab pipelined
IS
v_ret REFCUR_PKG.my_subquery_tab;
begin
if (p_cur_num = 1) then
if (upper(p_cur_type) = 'X') then
for rec in (select * from my_tab where col1=1 and col3='X')
loop
pipe row(rec);
end loop;
elsif (upper(p_cur_type) = 'Y') then
for rec in (select * from my_tab where col1=1 and col3='Y')
loop
pipe row(rec);
end loop;
else
return;
end if;
elsif (p_cur_num = 2) then
if (upper(p_cur_type) = 'X') then
for rec in (select * from my_tab where col1=2 and col3='X')
loop
pipe row(rec);
end loop;
elsif (upper(p_cur_type) = 'Y') then
for rec in (select * from my_tab where col1=2 and col3='Y')
loop
pipe row(rec);
end loop;
else
return;
end if;
end if;
return;
end;
MAIN procedure example
-- main procedure/usage
declare
cursor sel_cur1 is
with X as (select * from table(get_tab_data(1, 'x'))),
Y as (select * from table(get_tab_data(1, 'y')))
select X.col1, Y.col2 from X,Y where X.col1 = Y.col1;
begin
for rec in sel_cur1
loop
dbms_output.put_line(rec.col1 || ',' || rec.col2);
end loop;
end;
All of your various subqueries are reduced to a call to a single pipelined function, which determines the rows to return.
EDIT:
To combine all needed types and functions into 1 procedure, and also to use variables for subquery function parameters, I'm adding the following example:
create or replace procedure my_pipe
IS
-- define types
type my_subquery_tab is table of my_tab%rowtype;
type ref_cur_t is ref cursor;
v_ref_cur ref_cur_t;
-- define vars
v_with_sql varchar2(4000);
v_main_sql varchar2(32767);
v_x1 number;
v_x2 char;
v_y1 number;
v_y2 char;
v_col1 my_tab.col1%type;
v_col2 my_tab.col2%type;
-- define local functions/procs
function get_tab_data(p_cur_num in number, p_cur_type in char)
return my_subquery_tab pipelined
IS
v_ret my_subquery_tab;
begin
if (p_cur_num = 1) then
if (upper(p_cur_type) = 'X') then
for rec in (select * from my_tab where col1=1 and col3='X')
loop
pipe row(rec);
end loop;
elsif (upper(p_cur_type) = 'Y') then
for rec in (select * from my_tab where col1=1 and col3='Y')
loop
pipe row(rec);
end loop;
else
return;
end if;
elsif (p_cur_num = 2) then
if (upper(p_cur_type) = 'X') then
for rec in (select * from my_tab where col1=2 and col3='X')
loop
pipe row(rec);
end loop;
elsif (upper(p_cur_type) = 'Y') then
for rec in (select * from my_tab where col1=2 and col3='Y')
loop
pipe row(rec);
end loop;
else
return;
end if;
end if;
return;
end;
BEGIN
---------------------------------
-- Setup SQL for cursors
---------------------------------
-- this will have different parameter values for subqueries
v_with_sql := q'{
with X as (select * from table(get_tab_data(:x1, :x2))),
Y as (select * from table(get_tab_data(:y1, :y2)))
}';
-- this will stay the same for all cursors
v_main_sql := q'{
select X.col1, Y.col2 from X,Y where X.col1 = Y.col1
}';
---------------------------------
-- set initial subquery parameters
---------------------------------
v_x1 := 1;
v_x2 := 'x';
v_y1 := 1;
v_y2 := 'y';
open v_ref_cur for v_with_sql || v_main_sql using v_x1, v_x2, v_y1, v_y2;
loop
fetch v_ref_cur into v_col1, v_col2;
exit when v_ref_cur%notfound;
dbms_output.put_line(v_col1 || ',' || v_col2);
end loop;
close v_ref_cur;
---------------------------------
-- change subquery parameters
---------------------------------
v_x1 := 2;
v_x2 := 'x';
v_y1 := 2;
v_y2 := 'y';
open v_ref_cur for v_with_sql || v_main_sql using v_x1, v_x2, v_y1, v_y2;
loop
fetch v_ref_cur into v_col1, v_col2;
exit when v_ref_cur%notfound;
dbms_output.put_line(v_col1 || ',' || v_col2);
end loop;
close v_ref_cur;
end;
Note the benefit now is that even if you have many different cursors, you only need to define the main query and subquery SQL once. After that, you're just changing variables.
Cheers
--Create views that will be replaced by common table expressions later.
--The column names have to be the same, the actual content doesn't matter.
create or replace view x as select 'wrong' col1, 'wrong' col2 from dual;
create or replace view y as select 'wrong' col1, 'wrong' col2 from dual;
--Put the repetitive logic in one view
create or replace view main_select as
select count(x.col1) total, x.col2
from X inner join Y on x.col1 = y.col1
group by rollup (x.col1);
--Just querying the view produces the wrong results
select * from main_select;
--But when you add the common table expressions X and Y they override
--the dummy views and produce the real results.
declare
cursor cursor_1 is
with X as (select 'right' col1, 'right' col2 from dual),
Y as (select 'right' col1, 'right' col2 from dual)
select total, col2 from main_select;
--... repeat for each cursor, just replace X and Y as necessary
begin
for r in cursor_1 loop
dbms_output.put_line(r.col2);
end loop;
null;
end;
/
This solution is a little weirder than the pipelined approach, and requires 3 new objects for the views, but it will probably run faster
since there is less context switching between SQL and PL/SQL.
One possibility you could consider is using 2 Global Temporary Tables (GTTs) for X and Y. Then you just need one cursor, but you have to clear and re-populate the 2 GTTs several times - and if data volumes are large you may want to get optimiser stats on the GTTs each time too.
This is the sort of thing I mean:
cursor_gtt is
select count(X.col1), ...
from GTT_X inner join GTT_Y on...
group by rollup (X.col1, ...
begin
insert into gtt_x select col1, col2 from TAB where col1 = '1';
insert into gtt_y select col1, col2 from TAB where col2 = '3';
-- maybe get stats for gtt_x and gtt_y here
for r in cursor_gtt loop
print_report_results(r);
end loop;
delete gtt_x;
delete gtt_y;
insert into gtt_x select col1, col2 from TAB where col1 = '7' and col2 = '9' and col3 = 'TEST';
insert into gtt_y select col1, col2 from TAB where col3 = '6'
-- maybe get stats for gtt_x and gtt_y here
for r in cursor_gtt loop
print_report_results(r);
end loop;
...
end;
So the same 2 GTTs are re-populated and the same cursor is used each time.
What about creating a view for the main query? That pretties up your code and centralizes the main query to boot.

Resources