converting output of a Query into xml - oracle

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.

Related

How to get rid of compilation errors in SQL?

I keep getting the Warning 'Procedure created with compilation errors'.
The code is the following.
BEGIN
CREATE OR REPLACE PACKAGE ABC IS
TYPE names_varray IS VARRAY(10) OF VARCHAR2(30);
TYPE salaries_varray IS VARRAY(10) OF NUMBER;
names names_varray;
salaries salaries_varray;
PROCEDURE BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse;
END ABC;
CREATE OR REPLACE PACKAGE BODY ABC IS
CURSOR c_emp_top_salaries
IS
SELECT ROWNUM, ENAME, SAL
FROM (SELECT ENAME, SAL FROM EMP ORDER BY SAL DESC)
WHERE ROWNUM <= 10;
PROCEDURE BuildVarrays
IS
v_count NUMBER := 0;
BEGIN
FOR emp_rec IN c_emp_top_salaries LOOP
v_count := v_count + 1;
names(v_count) := emp_rec.ENAME;e
salaries(v_count) := emp_rec.SAL;
END LOOP;
END BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse
IS
BEGIN
FOR i IN REVERSE 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('Employee Name: ' || names(i) || ' Salary: ' || salaries(i));
END LOOP;
END DisplayEmpTop10SalariesInReverse;
END ABC;
BEGIN
ABC.BuildVarrays;
ABC.DisplayEmpTop10SalariesInReverse;
END;
END;
I have tried to rebuild the code, but I seem to be stuck. Being new to PL/SQL, I am unable to understand how to manage user-defined exceptions in procedures
Don't wrap it in BEGIN and END;
Use / on a new line as a PL/SQL statement terminator; and
Use BULK COLLECT rather than a cursor.
Like this:
CREATE OR REPLACE PACKAGE ABC IS
TYPE names_varray IS VARRAY(10) OF EMP.ENAME%TYPE;
TYPE salaries_varray IS VARRAY(10) OF EMP.SAL%TYPE;
names names_varray;
salaries salaries_varray;
PROCEDURE BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse;
END ABC;
/
CREATE OR REPLACE PACKAGE BODY ABC IS
PROCEDURE BuildVarrays
IS
BEGIN
SELECT ENAME, SAL
BULK COLLECT INTO names, salaries
FROM (SELECT ENAME, SAL FROM EMP ORDER BY SAL DESC)
WHERE ROWNUM <= 10;
END BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse
IS
BEGIN
FOR i IN REVERSE 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('Employee Name: ' || names(i) || ' Salary: ' || salaries(i));
END LOOP;
END DisplayEmpTop10SalariesInReverse;
END ABC;
/
and then call it using:
BEGIN
DBMS_OUTPUT.ENABLE();
ABC.BuildVarrays;
ABC.DisplayEmpTop10SalariesInReverse;
END;
/
Which, for the table:
CREATE TABLE emp (ename, sal) AS
SELECT 'Alice', 100 FROM DUAL UNION ALL
SELECT 'Beryl', 500 FROM DUAL UNION ALL
SELECT 'Carol', 400 FROM DUAL UNION ALL
SELECT 'Debra', 900 FROM DUAL UNION ALL
SELECT 'Emily', 700 FROM DUAL UNION ALL
SELECT 'Fiona', 800 FROM DUAL UNION ALL
SELECT 'Gemma', 200 FROM DUAL UNION ALL
SELECT 'Hanna', 800 FROM DUAL UNION ALL
SELECT 'Ivory', 400 FROM DUAL UNION ALL
SELECT 'Jesse', 600 FROM DUAL UNION ALL
SELECT 'Kiera', 500 FROM DUAL;
Outputs:
Employee Name: Gemma Salary: 200
Employee Name: Ivory Salary: 400
Employee Name: Carol Salary: 400
Employee Name: Kiera Salary: 500
Employee Name: Beryl Salary: 500
Employee Name: Jesse Salary: 600
Employee Name: Emily Salary: 700
Employee Name: Hanna Salary: 800
Employee Name: Fiona Salary: 800
Employee Name: Debra Salary: 900
fiddle

Stored procedure calling 2 values in parameter declaration

I have a stored procedure written like this:
CREATE OR REPLACE PROCEDURE Proc_location_example(in_data_ID IN VARCHAR2,
in_Location_name IN VARCHAR2)
IS
v_Location_ID NUMBER;
v_data_id NUMBER;
BEGIN
---------------------------------------------------------------------------
lv_prog_name := 'PRoc_location_example';
ln_step := 1;
SELECT Location_ID
INTO v_Location_ID
FROM random.client
WHERE Location_name = in_Location_name;
.....
END;
This procedure is getting 'NY ' passed in as an example for in_location_name. I want to pass in 'NY' and 'nj' to the location_name.
In other words the location name supports 2 name so which will be easiest way to do it?
You can add an expression with an IN operator such as
.. WHERE Location_name = in_Location_name AND in_Location_name IN ('NY','nj')
if case-sensitivity doesn't matter, then use
.. WHERE Location_name = in_Location_name AND REGEXP_LIKE(in_Location_name, 'Ny|nJ','i')
in order to restrict the parameters to those two values.
There are two options that I can think of that would satisfy your question.
Option #1: More Parameters
By adding more parameters you can use those parameters in your query for location IDs. If you are going to potentially more than 2 locations and don't want to keep having to add parameters, Option #2 is probably a better choice.
CREATE OR REPLACE PROCEDURE Proc_location_example (in_location_name1 IN VARCHAR2 DEFAULT NULL,
in_location_name2 IN VARCHAR2 DEFAULT NULL)
IS
TYPE location_id_t IS TABLE OF NUMBER;
v_location_ids location_id_t;
BEGIN
SELECT location_id
BULK COLLECT INTO v_location_ids
FROM (SELECT 101 AS location_id, 'NJ' AS location_name FROM DUAL
UNION ALL
SELECT 102, 'NY' FROM DUAL
UNION ALL
SELECT 103, 'MA' FROM DUAL)
WHERE location_name IN (in_location_name1, in_location_name2);
FOR i IN 1 .. v_location_ids.COUNT
LOOP
DBMS_OUTPUT.put_line ('ID #' || i || ': ' || v_location_ids (i));
END LOOP;
END;
/
BEGIN
Proc_location_example (in_location_name1 => 'NJ', in_location_name2 => 'NY');
END;
/
Option #2: Table Type Parameter
By using a table as your parameter, you can pass an unlimited number of "location names". This does require you to use a pre-defined table type, but this solution is more flexible in the number of "parameters"
CREATE OR REPLACE TYPE location_name_t IS TABLE OF VARCHAR2 (2);
CREATE OR REPLACE PROCEDURE Proc_location_example (in_locations location_name_t)
IS
TYPE location_id_t IS TABLE OF NUMBER;
v_location_ids location_id_t;
BEGIN
SELECT location_id
BULK COLLECT INTO v_location_ids
FROM (SELECT 101 AS location_id, 'NJ' AS location_name FROM DUAL
UNION ALL
SELECT 102, 'NY' FROM DUAL
UNION ALL
SELECT 103, 'MA' FROM DUAL)
WHERE location_name IN (select * from table(in_locations));
FOR i IN 1 .. v_location_ids.COUNT
LOOP
DBMS_OUTPUT.put_line ('ID #' || i || ': ' || v_location_ids (i));
END LOOP;
END;
/
BEGIN
Proc_location_example (in_locations => location_name_t('NJ','NY'));
END;
/

How can Avoid Oracle Ref Cursor

i have a develop a application that contain large no of rows i was using ref cursor but i do not need a cursor. how can achieve that.
here is below code i was using ref cursor please help me out how can avoid it.any idea
create or replace
PROCEDURE remove_emp
(
employee_id in NUMBER,
o_data out sys_refcursor
)
AS
v_account_status help.topic%type;
v_username help.seq%type;
v_lock_date help.info%type;
BEGIN
open o_data for
select topic,seq, info
into v_account_status,v_username,v_lock_date
from help;-- where seq=employee_id ;
DBMS_OUTPUT.put_line ('topic:'||v_account_status);
DBMS_OUTPUT.put_line ('seq:'||v_username);
DBMS_OUTPUT.put_line ('info:'||v_lock_date);
END remove_emp;
Just remove the cursor and just use SELECT ... INTO ...:
Oracle Setup:
CREATE TABLE help ( topic, seq, info ) AS
SELECT 'a', 1, 'aa' FROM DUAL UNION ALL
SELECT 'b', 2, 'bb' FROM DUAL UNION ALL
SELECT 'c', 3, 'cc' FROM DUAL;
Procedure:
create PROCEDURE remove_emp
(
employee_id in help.seq%type
)
AS
v_account_status help.topic%type;
v_username help.seq%type;
v_lock_date help.info%type;
BEGIN
select topic,seq, info
into v_account_status,v_username,v_lock_date
from help where seq=employee_id;
DBMS_OUTPUT.put_line ('topic:'||v_account_status);
DBMS_OUTPUT.put_line ('seq:'||v_username);
DBMS_OUTPUT.put_line ('info:'||v_lock_date);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.put_line ('seq:Not Found!');
END remove_emp;
/
Call the procedure:
BEGIN
remove_emp(1);
END;
/
Output:
topic:a
seq:1
info:aa
db<>fiddle here

Comma separated values to IN function in oracle

I am trying to execute below query, but not getting any result.
Could some one tell what wrong I am doing?.
DECLARE
object_types VARCHAR2(200);
v_object_types VARCHAR2(200);
l_count number;
BEGIN
object_types :='TABLE,VIEW';
select ''''||regexp_replace(object_types, '( )*(,)( )*',''',''')||''''
into v_object_types from dual;
dbms_output.put_line(to_char(v_object_types));
SELECT count(*) into l_count
FROM all_objects o where o.object_type IN ('||v_object_types||');
dbms_output.put_line(l_count);
END;
WHERE variable IN (1,2,3)
is different to what you are sending now
WHERE variable IN ('1,2,3') or
WHERE variable IN ('||v_object_types||')
You are trying to build the SQL dynamically but instead you are using a single string literal '||v_object_types||' in the IN clause.
You can do it using a collection:
Oracle Setup:
CREATE TYPE stringlist IS TABLE OF VARCHAR2(200);
/
PL/SQL:
DECLARE
object_types VARCHAR2(200) := 'TABLE,VIEW';
v_object_types stringlist;
BEGIN
SELECT TRIM( BOTH FROM REGEXP_SUBSTR( object_types, '[^,]+', 1, LEVEL ) )
BULK COLLECT INTO v_object_types
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( object_types, '[^,]+' );
FOR i IN 1 .. v_object_types.COUNT LOOP
dbms_output.put_line( v_object_types(i) );
END LOOP;
SELECT count(*)
INTO l_count
FROM all_objects
WHERE object_type MEMBER OF v_object_types;
dbms_output.put_line(l_count);
END;
/
or using dynamic sql:
DECLARE
object_types VARCHAR2(200) := 'TABLE,VIEW';
v_sql VARCHAR2(200);
l_count INTEGER;
BEGIN
v_sql := 'SELECT count(*) FROM all_objects WHERE object_type IN ('
|| REGEXP_REPLACE( object_types, ' *(.+?) *(,|$)', '''\1''\2' )
|| ')';
EXECUTE IMMEDIATE v_sql INTO l_count;
dbms_output.put_line(l_count);
END;
/

Get XMLType from a cursor

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>

Resources