Get XMLType from a cursor - oracle

Anyone knows if I can generate a XMLType from a cursor without having to specify each row's name manually ?
I would like to be able to loop over my query, and to get a separate XML for each row.
I couldn't get a solution using DBMS_XMLGEN.getXMLType, but perhaps I didn't use it properly.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor is
SELECT '1a' as "column1", '1b' as "column2" FROM DUAL
UNION ALL
SELECT '2a' as "column1", '2b' as "column2" FROM DUAL;
myXMLType XMLType;
BEGIN
FOR mySelect in mySelectCursor
LOOP
-- I would like to replace the following line of code
myXMLType := XMLType('<row><column1>' || mySelect."column1" || '</column1><column2>' || mySelect."column2" || '</column2></row>');
-- by something similar to this (not working) one
--myXMLType := mySelect.getXMLType();
dbms_output.put_line(myXMLType.getClobVal());
END LOOP;
END;
--The following code outputs
--<row><column1>1a</column1><column2>1b</column2></row>
--<row><column1>2a</column1><column2>2b</column2></row>

I finally have found a solution.
Thank you for your help.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor is
SELECT
VALUE(table_temp) as "XMLTYPE"
FROM
table(
XMLSequence(
Cursor(
SELECT '1a' as "column1", '1b' as "column2" FROM DUAL
UNION ALL
SELECT '2a' as "column1", '2b' as "column2" FROM DUAL
)
)
) table_temp;
BEGIN
FOR mySelect in mySelectCursor
LOOP
dbms_output.put_line('===');
dbms_output.put_line(mySelect."XMLTYPE".getClobVal());
END LOOP;
END;
-- Output is :
--===
-- <ROW>
-- <column1>1a</column1>
-- <column2>1b</column2>
-- </ROW>
--
--===
-- <ROW>
-- <column1>2a</column1>
-- <column2>2b</column2>
-- </ROW>

Here is the code snippet, you need to use DBMS_XMLGEN.getxml:
DECLARE
v_xml CLOB;
v_more BOOLEAN := TRUE;
BEGIN
v_xml := DBMS_XMLGEN.getxml('select * from dual');
dbms_output.put_line(v_xml);
END;
/
Here is the code snippet, to loop in a cursor
CREATE OR REPLACE TYPE test_type AS OBJECT (
col1 CHAR(2),
col2 CHAR(2)
);
/
create or replace view test_view
as SELECT '1a' as column1, '1b' as column2 FROM DUAL
UNION ALL
SELECT '2a' as column1, '2b' as column2 FROM DUAL;
DECLARE
CURSOR c_xml IS
SELECT SYS_XMLAGG(
SYS_XMLGEN(
test_type(column1, column2),
sys.xmlgenformatType.createFormat('TABLE')
),
sys.xmlgenformatType.createFormat('TEST_VIEW')
).getStringVal() AS xml_row
FROM test_view;
BEGIN
FOR cur_rec IN c_xml LOOP
dbms_output.put_line(cur_rec.xml_row);
END LOOP;
end;
/

Does this query fit you needs:
WITH t AS
(SELECT '1a' AS c1, '1b' AS c2 FROM DUAL
UNION ALL
SELECT '2a' AS c1, '2b' AS c2 FROM DUAL)
SELECT XMLELEMENT("row",
XMLELEMENT("column1", c1 ),
XMLELEMENT("column2", c2 )
) AS myXMLType
FROM t;
Another way would be this one:
CREATE OR REPLACE TYPE "row" AS OBJECT (
"column1" VARCHAR2(100),
"column2" VARCHAR2(100))
/
WITH t AS
(SELECT '1a' AS c1, '1b' AS c2 FROM DUAL
UNION ALL
SELECT '2a' AS c1, '2b' AS c2 FROM DUAL)
SELECT XMLTYPE("row"(c1,c2)) AS myXMLType
FROM t;

Here is the solution with XMLTABLE rather than XMLSEQUENCE.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor IS
SELECT
VALUE(table_temp) AS "XMLTYPE"
FROM
XMLTABLE('/ROWSET/ROW' PASSING
DBMS_XMLGEN.GETXMLTYPE('
SELECT ''1a'' as "column1", ''1b'' as "column2" FROM DUAL
UNION ALL
SELECT ''2a'' as "column1", ''2b'' as "column2" FROM DUAL
')
) table_temp;
BEGIN
FOR mySelect IN mySelectCursor
LOOP
dbms_output.put_line(mySelect."XMLTYPE".getClobVal());
END LOOP;
END;
-- Output is :
--<ROW><column1>1a</column1><column2>1b</column2></ROW>
--<ROW><column1>2a</column1><column2>2b</column2></ROW>

Related

converting output of a Query into xml

I have a table named emp with name, id, salary as column names.
My procedure takes id as input and it can be more than one id also. like('1','2','3',...)
Required: For each and every id the values should be returned as XML.
Supoose: For id=1 and id=2 as input my output should be
<row>
<name>name1</name>
<salary>salary1</salary>
</row>
<row>
<name>name2</name>
<salary>salary2</salary>
</row>
I am able to run the code to separate id's and get the output. But I am getting stuck at sending values as xml.
Code i tried:
procedure get_details(P_ID varchar2) as
begin
select * from emp where id in (
select regexp_substr(P_ID,'[^,]+', 1, level) from dual
connect BY regexp_substr(P_ID, '[^,]+', 1, level)
is not null);
end get_values;
Any input will be appreciated.
Edit regarding solutions: Both the solutions provided in db-fiddle are working perfectly fine.
Solution 1:
CREATE PROCEDURE get_details(
P_IDs IN varchar2
) IS
v_clob CLOB;
BEGIN
SELECT XMLELEMENT(
"root",
XMLAGG(
XMLELEMENT(
"row",
XMLFOREST(
name AS "name",
salary AS "salary"
)
)
)
).getClobVal()
INTO v_clob
FROM emp
where p_ids LIKE '%''' || id || '''%';
DBMS_OUTPUT.PUT_LINE( v_clob );
end get_details;
/
BEGIN
DBMS_OUTPUT.PUT_LINE( 'Your output:' );
get_details( '''1'',''3''' );
END;
/
Solution 2:
create or replace procedure get_details(p_id varchar2)
as
lo_xml_output clob;
begin
select to_clob(xmltype(cursor(select *
from emp
where id in
(select regexp_substr(p_id,'[^,]+', 1, level)
from dual
connect by regexp_substr(P_ID, '[^,]+', 1,level) is
not null))))
into lo_xml_output from dual;
DBMS_OUTPUT.PUT_LINE( lo_xml_output );
end get_details;
/
BEGIN
DBMS_OUTPUT.PUT_LINE('');
get_details('1,3');
END;
/
You can use the XML functions:
CREATE PROCEDURE get_details(
P_IDs IN varchar2
) IS
v_clob CLOB;
BEGIN
SELECT XMLELEMENT(
"root",
XMLAGG(
XMLELEMENT(
"row",
XMLFOREST(
name AS "name",
salary AS "salary"
)
)
)
).getClobVal()
INTO v_clob
FROM emp
where p_ids LIKE '%''' || id || '''%';
DBMS_OUTPUT.PUT_LINE( v_clob );
end get_details;
/
So, for the test data:
CREATE TABLE emp ( id, name, salary ) AS
SELECT 1, 'name1', 1000 FROM DUAL UNION ALL
SELECT 2, 'name2', 2000 FROM DUAL UNION ALL
SELECT 3, 'name3', 3000 FROM DUAL
The anonymous block:
BEGIN
get_details( '''1'',''3''' );
END;
/
Outputs:
<root><row><name>name1</name><salary>1000</salary></row><row><name>name3</name><salary>3000</salary></row></root>
db<>fiddle here
Another way will be by using xmltype,you can pass a query as a cursor to xmltype.
create or replace procedure get_details(p_id varchar2)
as
lo_xml_output clob;
begin
select to_clob(xmltype(cursor(select *
from emp
where id in
(select regexp_substr(p_id,'[^,]+', 1, level)
from dual
connect by regexp_substr(P_ID, '[^,]+', 1,level) is
not null))))
into lo_xml_output from dual;
DBMS_OUTPUT.PUT_LINE( lo_xml_output );
end get_details;
/
-- test
BEGIN
DBMS_OUTPUT.PUT_LINE('');
get_details('1,3');
END;
/
Db<>fiddle for your reference
P.S. we can also usedbms_xmlgen.getxml but then we need to build the SQL dynamically before passing it to dbms_xmlgen.getxml. you can see in the fiddle as well.

How to assign multiple values to a variable using into clause in oracle query?

I have an oracle stored procedure like this
CREATE OR REPLACE PROCEDURE DEMO (V_IN CHAR, V_OUT VARCHAR2)
IS
BEGIN
FOR ITEM IN LOOP (SELECT DISTINCT (NAME)
FROM TABLE1 INTO V_OUT
WHERE ID = V_IN
LOOP
--CODE TO PRINT V_OUT
END LOOP;
END;
Now how should I create that V_OUT variable so that it can hold all the values coming from query? I'm doing this in oracle12C.
You don't put the INTO clause in the cursor query. And even if you did, you have it in the wrong place in the SQL statement.
You deal with that when you fetch a row from the query:
CREATE OR REPLACE PROCEDURE
DEMO (V_IN CHAR, V_OUT
VARCHAR2)IS
BEGIN
FOR ITEM IN LOOP
(SELECT DISTINCT (NAME)
FROM TABLE1
WHERE ID= V_IN
)
LOOP
dbms_output.put_line(item.name);
v_out := item.name;
END LOOP;
END;
But then the problem is that we just keep overlaying the previous value, so that when your procedure actually exits, the only value of v_out is that last assigned. If you truely need a collection of values, you need to declare your output variable to be a ref cursor, and adjust code accordingly. I've never actually worked with them, so perhaps someone else will chime in.
You can work with collections, like this:
--declare the pakage type
CREATE OR REPLACE PACKAGE PKG_TYPES AS
TYPE LIST_VARCHAR IS TABLE OF VARCHAR2(2000);
END;
--create the proc that will assemble the value list
CREATE OR REPLACE PROCEDURE DEMO ( V_IN IN varchar2, V_OUT IN OUT PKG_TYPES.LIST_VARCHAR) IS
BEGIN
FOR ITEM IN (
SELECT DISTINCT (NAME) name
FROM (SELECT 'X' ID, 'A' name FROM dual
UNION
SELECT 'X' ID, 'b' name FROM dual
UNION
SELECT 'y' ID, 'c' name FROM dual
) TABLE1
WHERE ID= V_IN
)
LOOP
V_OUT.EXTEND;
V_OUT(V_OUT.LAST) := item.name;
--CODE TO PRINT V_OUT
END LOOP;
END;
--use the list. I separated this step but it can be in the demo proc as well
DECLARE
names PKG_TYPES.LIST_VARCHAR := PKG_TYPES.LIST_VARCHAR();
BEGIN
demo('X',names) ;
FOR i IN names.first..names.last LOOP
Dbms_Output.put_line(i);
END LOOP;
END;
You will have to handle exceptions for when no value is returned from the cursor (when no ID is found).
If you need a collection variable - you can use a nested table and bulk collect like below.
To be able to return the value from the procedure you will need to declare the nested table type in some package or on DB schema level.
declare
type test_type is table of varchar2(2000);
test_collection test_type;
begin
select distinct(name) bulk collect into test_collection
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1;
for i in test_collection.first..test_collection.last loop
dbms_output.put_line(test_collection(i));
end loop;
end;
/
If you just need a string with concatenated values - you can use listagg to create it like below
declare
test_str varchar2(4000);
begin
select listagg(name, ', ') within group(order by 1)
into test_str
from (
select distinct name
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1
);
dbms_output.put_line(test_str);
end;
/

(Oracle)How to test user function convenently without making another table

To test function 'test2' below, I have tried like these.
SELECT test2('A') FROM DUAL;SELECT test2('C') FROM DUAL;SELECT test2('E') FROM DUAL;
But is there a convenient way to do this at once ? (without making another table)
I guess query might look like this
SELECT test2(p.c1) FROM ( .... ) p ?
Table and function as below
CREATE TABLE T2 (
C1 VARCHAR2(1),
C2 NUMBER
);
INSERT INTO T2 VALUES ('A',1);
INSERT INTO T2 VALUES ('B',4);
INSERT INTO T2 VALUES ('C',3);
INSERT INTO T2 VALUES ('D',2);
INSERT INTO T2 VALUES ('E',4);
CREATE OR REPLACE FUNCTION test2
(p1 IN VARCHAR2)
RETURN NUMBER AS V_VALUE NUMBER;
BEGIN
SELECT(
SELECT C2
FROM T2
WHERE C1=p1)
INTO V_VALUE
FROM DUAL;
RETURN V_VALUE;
END;
/
I think you might be after something like:
WITH test_data AS (SELECT 'A' val FROM dual UNION ALL
SELECT 'B' val FROM dual UNION ALL
SELECT 'C' val FROM dual UNION ALL
SELECT 'D' val FROM dual UNION ALL
SELECT 'E' val FROM dual)
SELECT test2(val)
FROM test_data;
Using select ... from dual union all select ... from dual ... is a great way of setting up temporary test data for simple tests, without needing to create extra objects to hold the data.

How to change alias to table name automatically in case of oracle?

We are currently analyzing hundreds of queries.
E.g
SELECT a.id,
a.name,
a.hobby,
b.desc
FROM tablename a,
table2 b
WHERE a.id = b.id;
or
SELECT id,
NAME,
hobby
FROM tablename;
above these can be change like below?
SELECT tablename.id,
tablename.name,
tablename.hobby,
table2.desc
FROM tablename tablename,
table2 table2
WHERE tablename.id = table2.id;
or
SELECT tablename.id,
tablename.name,
tablename.hobby
FROM tablename tablename;
Do you know any tools or methods that have the ability to change them?
Here is the Ananymous block for another query :
declare
v_query varchar2(4000) :='SELECT a.id,
a.name,
a.hobby,
b.desc
FROM tablename a,
table2 b
WHERE a.id = b.id';
tab_list varchar2(4000);
v_newquery varchar2(4000);
v_tab_alias varchar2(100);
v_tabname varchar2(500);
v_alias varchar2(40);
tab_cnt NUMBER :=0;
begin
-- table list
select replace(REGEXP_SUBSTR(regexp_replace(v_query,'FROM|WHERE','#'),'[^#]+',1,2)||chr(10),chr(10),null)
into tab_list
from dual;
-- no of tables
select REGEXP_COUNT(TRIM(REGEXP_SUBSTR(regexp_replace(v_query,'FROM|WHERE','#'),'[^#]+',1,2)),',')+1
into tab_cnt
from dual;
For i in 1..tab_cnt
LOOP
select TRIM(REGEXP_SUBSTR(tab_list,'[^,]+',1,i))
into v_tab_alias
from dual;
-- replace alias tablename for the column list
select TRIM(REGEXP_SUBSTR(v_tab_alias,'[^ ]+',1,1))
into v_tabname
from dual;
select TRIM(REGEXP_SUBSTR(v_tab_alias,'[^ ]+',1,2))
into v_alias
from dual;
select regexp_replace(v_query,v_alias||'\.',v_tabname||'.')
into v_newquery
from dual;
v_query:= v_newquery;
-- replace alias tablename in FROM clause
select regexp_replace(v_query,v_alias||'\,',v_tabname||',')
into v_query
from dual;
END LOOP;
-- replace alias last tablename before WHERE clause
select regexp_replace(v_query,v_tab_alias,v_tabname||' '||v_tabname)
into v_query
from dual;
DBMS_OUTPUT.PUT_LINE(v_query);
END;

Bulk Collect Twice over same nested table

Is there any way that after the second bulk collect, data does not get override of the first bulk collect. I don't want to iterate in loop.
DECLARE
TYPE abc IS RECORD (p_id part.p_id%TYPE);
TYPE abc_nt
IS
TABLE OF abc
INDEX BY BINARY_INTEGER;
v_abc_nt abc_nt;
BEGIN
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E1', 'E2');
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E3', 'E4');
FOR i IN v_abc_nt.FIRST .. v_abc_nt.LAST
LOOP
DBMS_OUTPUT.put_line (
'p_id is ' || v_abc_nt (i).p_id
);
END LOOP;
END;
OUTPUT:
p_id is E3
p_id is E4
Note: E1 and E2 is present in part table.
You can't simply add the data to the collection, no.
You can, however, do a BULK COLLECT into a separate collection and then combine the collections assuming that you really just need/ want a nested table rather than an associative array...
DECLARE
TYPE abc IS RECORD (p_id part.p_id%TYPE);
TYPE abc_nt
IS
TABLE OF abc;
v_abc_nt abc_nt;
v_abc_nt2 abc_nt;
BEGIN
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E1', 'E2');
SELECT p_id
BULK COLLECT
INTO v_abc_nt2
FROM part
WHERE p_id IN ('E3', 'E4');
v_abc_nt := v_abc_nt MULTISET UNION v_abc_nt2;
FOR i IN v_abc_nt.FIRST .. v_abc_nt.LAST
LOOP
DBMS_OUTPUT.put_line (
'p_id is ' || v_abc_nt (i).p_id
);
END LOOP;
END;
If you really want to use an associative array, you would need to write some code because there is no way for Oracle to know automatically how to remap the associations of one array when you combine it with another associative array that has some of the same keys.
You can write it like this
bad example:
declare
type t_numb is record(
numb number);
type t_numb_list is table of t_numb;
v_numb_list t_numb_list;
begin
with q as
(select 1 a from dual union select 2 from dual union select 3 from dual)
select q.a bulk collect into v_numb_list from q;
with w as
(select 4 a from dual union select 5 from dual union select 6 from dual)
select w.a bulk collect into v_numb_list from w;
for r in 1 .. v_numb_list.count loop
dbms_output.put_line(v_numb_list(r).numb);
end loop;
end;
and this works good:
declare
type t_numb is record(
numb number);
type t_numb_list is table of t_numb;
v_numb_list t_numb_list := t_numb_list();
v_numb t_numb;
begin
for q in (select 1 a
from dual
union
select 2
from dual
union
select 3
from dual) loop
v_numb.numb := q.a;
v_numb_list.extend;
v_numb_list(v_numb_list.count) := v_numb;
end loop;
for w in (select 4 a
from dual
union
select 5
from dual
union
select 6
from dual) loop
v_numb.numb := w.a;
v_numb_list.extend;
v_numb_list(v_numb_list.count) := v_numb;
end loop;
for r in 1 .. v_numb_list.count loop
dbms_output.put_line(v_numb_list(r).numb);
end loop;
end;

Resources