Oracle : how to fetch data from dynamic query? - sql

I have a program to generate dynamic query string based on input. This query may select from any tables or joined tables in my DB, and the column names and number of columns are unknown.
Now with this query string as the only input, I want to fetch all data from the result and output them line by line, is there any way to do this ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Thank Thinkjet for the reference. I have solved the problem, to help the others, here is the piece of code I used:
DECLARE
v_curid NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_colcnt NUMBER;
v_name_var VARCHAR2(10000);
v_num_var NUMBER;
v_date_var DATE;
v_row_num NUMBER;
p_sql_stmt VARCHAR2(1000);
BEGIN
v_curid := DBMS_SQL.OPEN_CURSOR;
p_sql_stmt :='SELECT * FROM emp';
DBMS_SQL.PARSE(v_curid, p_sql_stmt, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(v_curid, v_colcnt, v_desctab);
-- Define columns:
FOR i IN 1 .. v_colcnt LOOP
IF v_desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_num_var);
ELSIF v_desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_date_var);
ELSE
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_name_var, 50);
END IF;
END LOOP;
v_row_num := dbms_sql.execute(v_curid);
-- Fetch rows with DBMS_SQL package:
WHILE DBMS_SQL.FETCH_ROWS(v_curid) > 0 LOOP
FOR i IN 1 .. v_colcnt LOOP
IF (v_desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
ELSIF (v_desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
ELSIF (v_desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_curid);
END;
/

You can do that with DBMS_SQL package.
Update
To get more detailed reference about DBMS_SQL go here.

If you are building your string within PL/SQL, you can run it with EXECUTE IMMEDIATE. <- link. Use the BULK COLLECT INTO and output the collection.

<PRE>
DECLARE
RUN_S CLOB;
IGNORE NUMBER;
SOURCE_CURSOR NUMBER;
PWFIELD_COUNT NUMBER DEFAULT 0;
L_DESCTBL DBMS_SQL.DESC_TAB2;
Z_NUMBER NUMBER;
BEGIN
RUN_S := ' SELECT 1 AS VAL1,
2 AS VAL2,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR1,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR2
FROM DUAL';
SOURCE_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SOURCE_CURSOR, RUN_S, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(SOURCE_CURSOR, PWFIELD_COUNT, L_DESCTBL); -- get record structure
FOR I IN 1 .. PWFIELD_COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Type:' || L_DESCTBL(I).COL_TYPE);
IF L_DESCTBL(I).COL_TYPE = 2 THEN
DBMS_SQL.DEFINE_COLUMN(SOURCE_CURSOR, I, Z_NUMBER);
END IF;
NULL;
END LOOP;
IGNORE := DBMS_SQL.EXECUTE(SOURCE_CURSOR);
LOOP
IF DBMS_SQL.FETCH_ROWS(SOURCE_CURSOR) > 0 THEN
FOR I IN 1 .. PWFIELD_COUNT LOOP
IF L_DESCTBL(I).COL_TYPE IN (2) THEN
DBMS_SQL.COLUMN_VALUE(SOURCE_CURSOR, I, Z_NUMBER);
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Value:' || Z_NUMBER);
END IF;
END LOOP;
ELSE
EXIT;
END IF;
END LOOP;
END;
</PRE>

Related

Change from Default Date to preferred date in this DBMS_SQL?

This code is from the proposed solution:
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:9541646100346616701
My query is 'select * from table'
The second column is a DATE and this routine writes it as appears in my IDE session ('DD-MON-YY');
How can I report the values in DATE columns to 'YYYY-MM-DD'?
procedure clob_maker(p_query varchar2) is
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_descTbl dbms_sql.desc_tab;
l_colCnt number;
n number := 0;
l_data clob;
begin
dbms_sql.parse( l_theCursor, p_query, dbms_sql.native );
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
for i in 1 .. l_colCnt loop
dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);
end loop;
l_status := dbms_sql.execute(l_theCursor);
while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
--dbms_output.put_line(l_columnValue); --this puts every column on a separate line
l_data := l_data || l_columnValue ||',';
end loop;
dbms_output.put_line(l_data);
n := n + 1;
end loop;
--dbms_output.put_line(l_data);
end clob_maker;
clob_maker('select * from mlb_catcher_birthdays fetch first 10 rows only');
The default format in which dates are represented is controlled by NLS parameter ns_date_format. You can just change this parameter at session level:
alter session set nls_date_format = 'YYYY-MM-DD';
You could also use TO_CHAR(), with the same format specifier as second argument - but this does not fit very well to your use case, since you would then need to check the datatype of each column before printing its value.

While Loop and If/Else in PL/SQL

I am trying to write a procedure that will produce the following output
exec WinOrLose(4)
Welcome to the Win or Lose Game. Your number is 4.
You win.
You lose.
You win.
You lose.
==> You lose!
So far I have this:
CREATE or REPLACE Procedure WinOrLose (
p_choice number ) AS
v_answer number;
DECLARE
v_answer := p_choice
BEGIN
dbms_output.put_line ('Welcome to the Win or Lose Game. Your number is ' ||
v_answer);
FOR v_answer in 1..10
IF MOD(v_answer, 2) = 0 THEN -- v_answer is even
dbms_output.put_line (You lose)
END;
/
I'm unsure of where to go from there. My thought process (psuedocode) is this:
SET v_answer := 1
While Loop (outside)
MOD(v_answer,2) = 0 then dbms.output (YOU LOSE)
ELSE
dbms.output (YOU WIN)
end if;
v_answer := p_choice
CREATE or REPLACE Procedure WinOrLose (
p_choice number ) AS
BEGIN
dbms_output.put_line ('Welcome to the Win or Lose Game. Your number is ' ||
p_choice);
FOR v_counter in 1..p_choice LOOP
IF (MOD(v_counter, 2) = 0)
THEN
dbms_output.put_line ('You win');
ELSE
dbms_output.put_line ('You lose');
END IF;
END LOOP;
IF (MOD(p_choice , 2) = 0)
THEN
dbms_output.put_line ('==> You win!');
ELSE
dbms_output.put_line ('==> You lose!');
END IF;
END;
/

Oracle pl/sql Change Array Size and Members After Delete

For example i have an array like
"a(1):=1 ,a(2):=2, a(3) := 3"
and now my array count =3 "(a.count)"
then i delete middle member "a.delete(2)" then i wanna make my array like this "a(1):=1;a(2):=3" and my array count = 2 ("a.count") how can i do this ?
ps:i need to this with big sized array so i think i should use, for or while loop but how...
The collection where you have deleted some element is called sparse collection. Below you have example how to iterate that type of collection and how to use it with forall.
declare
type a is table of number;
ar a;
v_idx number;
begin
select level bulk collect into ar from dual connect by level< 1000;
ar.delete(1);
ar.delete(4);
ar.delete(10);
ar.delete(88);
v_idx := ar.first;
while v_idx is not null loop
dbms_output.put_line('idx: '||v_idx ||' value:'|| ar(v_idx));
v_idx := ar.next(v_idx);
end loop;
-- FORALL i IN INDICES OF ar
-- INSERT INTO test_table VALUES ar(i);
end;
Thank you but i should change array too , i need to take same output when i print array members like
for i in ar.first..ar.last loop
dbms_output.put_line(ar(i));
end loop;
declare
type a is table of number;
ar a;
begin
select level bulk collect into ar from dual connect by level< 1000;
ar.delete(1);
ar.delete(4);
ar.delete(10);
ar.delete(88);
-- ar is sparse collection;
ar := ar MULTISET intersect ar;
-- ar is dense collection and for i in .... is possible
FOR i IN ar.first .. ar.last LOOP
DBMS_OUTPUT.put_line(ar(i));
END LOOP;
end;
you can try this approach assign values of first spared collection to second continues collection and use second collection for further processing...
declare
type num_arr is table of number;
v_num_arr1 num_arr; --first collection
v_num_arr2 num_arr := num_arr(); -- second collection initialization and declaration
v_idx number;
v_col_index number := 1;
begin
-- fill 10 element.
select level || '1' as num1 bulk collect into v_num_arr1 from dual connect by level < 10;
for x in v_num_arr1.first .. v_num_arr1.last loop
dbms_output.put_line('index: ' || x || ' value: ' || v_num_arr1(x));
end loop;
dbms_output.put_line('');
-- delete element
v_num_arr1.delete(3);
v_num_arr1.delete(7);
v_idx := v_num_arr1.first;
while v_idx is not null loop
dbms_output.put_line('index: ' || v_idx || ' value: ' || v_num_arr1(v_idx));
-- filling second collection with regular index by variable v_col_index
if v_num_arr1(v_idx) is not null then
v_num_arr2.extend(1);
v_num_arr2(v_col_index) := v_num_arr1(v_idx);
v_col_index := v_col_index + 1;
end if;
v_idx := v_num_arr1.next(v_idx);
end loop;
dbms_output.put_line('second collection elements
');
--check second colleciton
for x in v_num_arr2.first .. v_num_arr2.last loop
dbms_output.put_line('index: ' || x || ' value: ' || v_num_arr2(x));
end loop;
end;

pl sql query takes more time to execute

I found this PL/SQL at my workplace and I couldn't find the reason why this script takes so much time to execute:
DECLARE
query VARCHAR(500);
ref_cur REFCURSOR;
product_listH VARCHAR(1000):='';
product_listA VARCHAR(1000):='';
product_listP VARCHAR(1000):='';
product VARCHAR(100):='';
begin
query := ' select hotelname
from sch1.resconfirmsv rr,
sch1.reshoteldetailssv hd,sch2.respkgconfirmsv r '||
' where rr.id = hd.resconfirmid and
hd.resconfirmid = r.hotelconfirmid and
r.id = ' || m_resconfirmid || '';
OPEN ref_cur FOR EXECUTE query;
LOOP
FETCH ref_cur INTO product;
IF NOT FOUND THEN
EXIT; -- exit loop
END IF;
product_listH := product_listH||''||trim(COALESCE(product,'-'))||',<br>';
END LOOP;
product_listH := rtrim(trim(product_listH),',<br>');
CLOSE ref_cur;
query := ' select distinct programname
from sch1.resconfirmsv rr,
sch3.resactivitysv a,
sch3.resprogramsv hx,
sch2.respkgconfirmsv r '||
' where rr.id = hx.resconfirmid and
hx.id=a.resprogramid and
hx.resconfirmid = r.activitiesconfirmid and
r.id = ' || m_resconfirmid || '';
OPEN ref_cur FOR EXECUTE query;
LOOP
FETCH ref_cur INTO product;
IF NOT FOUND THEN
EXIT; -- exit loop
END IF;
product_listA := product_listA||''||trim(COALESCE(product,'-'))||',<br>';
END LOOP;
product_listA := rtrim(trim(product_listA),',<br>');
CLOSE ref_cur;
product_listP := product_listH || ',<br>' || product_listA;
product_listP := rtrim(trim(product_listP),',<br>');
product_listP = ltrim(rtrim(product_listP,',<br>'),',<br>');
RETURN product_listP;
end;
without this script total run-time is 12.176 sec and with this script it takes up to 18.802 sec.means this gets at least 6 seconds to execute. All the needed columns are indexed. Anybody can tell me where the places need to be more optimize in this query?
Why declaring the cursor as a seperate varchar?
Instead i'd use the normal declaration of a cursor, it takes time to analyze the query to be executed by Oracle, so (between the declare- and Begin-labels of your current code:
cursor ref_cur as
select distinct programname
from sch1.resconfirmsv rr,
sch3.resactivitysv a,
sch3.resprogramsv hx,
sch2.respkgconfirmsv r '||
where rr.id = hx.resconfirmid and
hx.id=a.resprogramid and
hx.resconfirmid = r.activitiesconfirmid and
r.id = m_resconfirmid;
Now you can use
For x in ref_cur loop
The same thing for query#2.
Cheers

String Split/ String replace based on character length

Here my problem in a notes column having 2000 characters max, i want my string output based on 35 characters, ya i need to replace <br> tag after 30 characters in the string.. example string "hi hello how are you doing out there, need your help!", i need out put as "hi hello how are you doing out<br> there, need your help! similar i need to calculate the sting length and have to split it 35+35+35.. i don't know how to perform this in sql/plsql.
select substr(note,1,(instr(note, ' ',35)))||'<br>'||substr(note,instr(note, ' ',35),
(instr(note, ' ',35)))notes from test
DECLARE
CURSOR notes_cur IS
SELECT 1 note_id, 'hi hello how are you doing out there, need your help! hi hello how are you doing out there, need your help!' note FROM DUAL UNION ALL
SELECT 2, 'hi hello how are you doing out there, need your help! hi hello how are you doing out there, need your help!' note FROM DUAL;
TYPE notes_ntt IS TABLE OF notes_cur%ROWTYPE;
l_notes notes_ntt;
l_loop_counter NUMBER;
l_split notes_ntt := notes_ntt();
l_space_start NUMBER;
l_string_start NUMBER;
l_space_position NUMBER;
BEGIN
OPEN notes_cur;
FETCH notes_cur BULK COLLECT INTO l_notes;
CLOSE notes_cur;
FOR indx IN 1..l_notes.COUNT LOOP
l_space_start := 33;
l_string_start := 1;
l_loop_counter := TRUNC(LENGTH(l_notes(indx).note) / 35);
FOR note IN 1..l_loop_counter LOOP
l_split.EXTEND;
l_split(l_split.LAST).note_id := l_notes(indx).note_id;
l_space_position := INSTR(l_notes(indx).note, CHR(32), l_space_start, 1);
l_split(l_split.LAST).note := SUBSTR
(
l_notes(indx).note
, l_string_start
, CASE
WHEN l_space_position = 0
THEN l_string_start
ELSE l_space_position - l_string_start
END
) || CHR(10);
l_space_start := l_space_position + 33;
l_string_start := l_space_position + 1;
END LOOP;
END LOOP;
FOR indx IN 1..l_split.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(l_split(indx).note_id || ' ' || l_split(indx).note);
NULL;
END LOOP;
END;
/*
1 hi hello how are you doing out there,
1 need your help! hi hello how are
1 you doing out there, need your help!
2 hi hello how are you doing out there,
2 need your help! hi hello how are
2 you doing out there, need your help!
*/
You could do this:
declare
l_in_string varchar2(1000) := 'hi hello how are you doing out there, need your help!';
l_out_string varchar2(1000);
begin
while length(l_in_string) > 35 loop
l_out_string := l_out_string || substr(l_in_string, 1, 35) || '<br>';
l_in_string := substr(l_in_string, 36);
end loop;
l_out_string := l_out_string || l_in_string;
dbms_output.put_line(l_out_string);
end;
However this is quite likely to break mid-word e.g.
hi hello how are you doing out there, need your help!
You would need to write more sophisticated code if you want to break on spaces only.