Representation of xmlcast result with delimeters in Pl/SQL - sql

I am a bit new to PL/SQL, I have a following problem:
I have xml with following structure(b.response):
<a>
<b>
...
<ruleList>
<number>2</number>
<rule>
<name>test1</name>
</rule>
<rule>
<name>test2</name>
</rule>
</ruleList>
</b>
</a>
I use the following script to parse needed values from XML:
SELECT
xmlcast(xmlquery('/a/b/ruleList/number' passing b.response returning content)as varchar2(1000)) rules_number,
xmlcast(xmlquery('/a/b/ruleList/rule[*]/name'passing b.response returning content)as varchar2(1000)) values
FROM test b
And have following result:
rules_number | values
2 | test1test2
My question is how can I enter delimeters between names using xmlcast? So the result should be following:
rules_number | values
2 | test1, test2

SELECT
xmlcast(xmlquery('string-join(/a/b/ruleList/number/text(),",")' passing b.response returning content)as varchar2(1000)) rules_number,
xmlcast(xmlquery('string-join(/a/b/ruleList/rule[*]/name/text(),",")' passing b.response returning content)as varchar2(1000)) values
FROM test b
/text() is extracting value of node not full node
string-join - join string sequence using delimiter
Better choice in your case is xmltable.
select * from xmltable( '/a/b/ruleList' passing xmltype( '<a>
<b>
<ruleList>
<number>2</number>
<rule>
<name>test1</name>
</rule>
<rule>
<name>test2</name>
</rule>
</ruleList>
</b>
</a>')
columns
rules_number varchar2(1000) path 'string-join(number/text(),",")',
"values" varchar2(1000) path 'string-join(rule/name/text(),",")'
);

Related

Get values from xml nodes within sql statement

I have string data with xml format in "Input" column from which I need specific values of nodes.
As an example:
I need every "error_text_1" value from each "error id".
<error_protokoll>
<header>
<source>machine</source>
</header>
<error_list>
<error id='0'>
<error_text_1>error0</error_text_1>
</error>
<error id='1'>
<error_text_1>error1</error_text_1>
</error>
</error_list>
</error_protokoll>
The following sql statement returns only the "error_text_1" value of id=0.
SELECT top (10)
XML_INPUT.value('(error_protokoll/error_list/error/error_text_1)[1]', 'varchar(200)') error
FROM
(
convert(xml, SUBSTRING( REPLACE(INPUT, '{
"xml" : "<?xml version=''1.0''?>', ''), 0 , len(REPLACE(INPUT, '{
"xml" : "<?xml version=''1.0''?>', ''))-3)) as XML_INPUT
FROM [Storage].[ods].[table]
) a
Instead of only the first item, I'd like to have all of them.
Could you please help how to solve this?
You need to use nodes in the FROM so that you get 1 row per error element, then you can get the value of error_text_1 for each one:
DECLARE #xml xml = '<error_protokoll>
<header>
<source>machine</source>
</header>
<error_list>
<error id="0">
<error_text_1>error0</error_text_1>
</error>
<error id="1">
<error_text_1>error1</error_text_1>
</error>
</error_list>
</error_protokoll>';
SELECT X.e.value('(./error_text_1/text())[1]','varchar(200)') AS error_text_1
FROM #xml.nodes('error_protokoll/error_list/error')X(e);
If the XML is in a table, your FROM would look like:
FROM dbo.YourTable YT
CROSS APPLY YT.YourColumn.nodes('error_protokoll/error_list/error')YC(e);

I want to get the condition for each tag and have table like this in sql

how can get the data from different tag of xml in sql
output
tag condition
_________________ ____________
GroupHeaderBand1 nature
GroupHeaderBand2 job
GroupHeaderBand3 name
input:
<StiSerializer version="1.02" type="Net" application="StiReport">
<Pages isList="true" count="1">
<Page1 Ref="3" type="Page" isKey="true">
<Components isList="true" count="12">
<GroupHeaderBand1 Ref="18" type="GroupHeaderBand" isKey="true">
<Condition>Nature</Condition>
</GroupHeaderBand1>
<GroupHeaderBand2 Ref="21" type="GroupHeaderBand" isKey="true">
<Condition>Job</Condition>
</GroupHeaderBand2>
<GroupHeaderBand3 Ref="26" type="GroupHeaderBand" isKey="true">
<Condition>Name</Condition>
</GroupHeaderBand3>
</Components>
</Page1>
this is my xml code
use following query
SELECT
A.evnt.value('local-name(.)', 'varchar(100)') AS tag,
A.evnt.value('(.)[1]','varchar(100)') as condition
FROM #XML.nodes('StiSerializer/Pages/Page1/Components/*') A(evnt)
or use
SELECT
A.evnt.value('local-name(.)', 'varchar(100)') AS tag,
A.evnt.value('(Condition/text())[1]','varchar(100)') as condition
FROM #XML.nodes('StiSerializer/Pages/Page1/Components/*') A(evnt)
demo in db<>fiddle

Delete Empty tag from xmltype oracle

i want try to delete the empty tag from xmltype. I Have generate the below xml from oracle type. In the collection few elements does not have values so i generated with empty tag.
Can any one please help me out:
Actual output:
<MESSAGE>
<LOCATIONS>
<LOCATION_ID>9999</LOCATION_ID>
<LOC_TYPE>S</LOC_TYPE>
<NAME>Test Location</NAME>
<PHONE_NUM>08 </PHONE_NUM>
<LAST_MODIFIED_BY/>
<LAST_MODIFIED_DATE/>
<POS_CODE/>
</LOCATIONS>
</MESSAGE>
Expected output:
<MESSAGE>
<LOCATIONS>
<LOCATION_ID>9999</LOCATION_ID>
<LOC_TYPE>S</LOC_TYPE>
<NAME>Test Location</NAME>
<PHONE_NUM>08 </PHONE_NUM>
</LOCATIONS>
</MESSAGE>
Use DELETEXML and look for the XPath //*[not(text())][not(*)] to find elements that contain no text and no children:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( xml ) AS
SELECT XMLTYPE( '<MESSAGE>
<LOCATIONS>
<LOCATION_ID>9999</LOCATION_ID>
<LOC_TYPE>S</LOC_TYPE>
<NAME>Test Location</NAME>
<PHONE_NUM>08 </PHONE_NUM>
<LAST_MODIFIED_BY/>
<LAST_MODIFIED_DATE/>
<POS_CODE/>
</LOCATIONS>
</MESSAGE>' ) FROM DUAL;
Query 1:
SELECT DELETEXML(
xml,
'//*[not(text())][not(*)]'
).getStringVal()
FROM table_name
Results:
| DELETEXML(XML,'//*[NOT(TEXT())][NOT(*)]').GETSTRINGVAL() |
|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| <MESSAGE><LOCATIONS><LOCATION_ID>9999</LOCATION_ID><LOC_TYPE>S</LOC_TYPE><NAME>Test Location</NAME><PHONE_NUM>08 </PHONE_NUM></LOCATIONS></MESSAGE> |
SELECT
deletexml(xml_data, '//*[not(text())][not(*)]').getstringval()
FROM
(
SELECT
xmltype('<MESSAGE>
<LOCATIONS>
<LOCATION_ID>9999</LOCATION_ID>
<LOC_TYPE>S</LOC_TYPE>
<NAME>Test Location</NAME>
<PHONE_NUM>08 </PHONE_NUM>
<LAST_MODIFIED_BY/>
<LAST_MODIFIED_DATE/>
<POS_CODE/>
</LOCATIONS>
</MESSAGE>'
) xml_data
FROM
dual
)
this is working fine thanks

How to pull XML key "value" from SQL CLOB

I am attempting to extract information from XML stored in a CLOB column. I've searched the forums and thus far have been unable to get the data to pull as needed. I have a basic understanding of SQL but this is beyond me.
The XML is similar to the following:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Header>
<OrderNum value="12354321"/>
<ExtractDate value="11-30-2012"/>
<RType value="Status"/>
<Company value="Company"/>
</Header>
<Body>
<Status>
<Order>
<ActivityType value="ValidateRequest"/>
<EndUser>
<Name value="Schmo, Joe"/>
<Address>
<SANO value="12345"/>
<SASN value="Mickey Mouse"/>
<SATH value="Lane"/>
<SASS value="N"/>
<City value="Orlando"/>
<State value="FL"/>
<Zip value="34786"/>
<Number value="5550000"/>
</Address>
</EndUser>
<COS value="1"/>
<TOS value="3"/>
<MainNumber value="5550000"/>
</Order>
<ErrorCode value="400"/>
<ErrorMessage value="RECEIVED"/>
</Status>
</Body>
</Response>
I want to get the values under "Address".
I've tried the following but it returns "NULL".
SELECT EXTRACTVALUE(XMLTYPE(RESPONSE_CLOB),'/Response/Body/Status/Order/EndUser/Address/SANO') AS SANO
FROM RESPONSE_TABLE
WHERE ROWNUM < 2
I am trying to get it so I can pull the "12345" assigned as "value" in "SANO" (ultimately getting the value for other fields, but want to at least get the one working first).
You're currently retrieving the text value of the node, but 12345 is the value attribute of the element rather than its text content. So you would need to use the #attribute syntax, i.e.:
SELECT EXTRACTVALUE(XMLTYPE(RESPONSE_CLOB),'/Response/Body/Status/Order/EndUser/Address/SANO/#value') AS SANO
FROM RESPONSE_TABLE
WHERE ROWNUM < 2;
SANO
--------------------
12345
But extractvalue is deprecated; assuming you're on a recent version of Oracle it would be better to use an XMLQuery:
SELECT XMLQUERY(
'/Response/Body/Status/Order/EndUser/Address/SANO/#value'
PASSING XMLTYPE(RESPONSE_CLOB)
RETURNING CONTENT
) AS SANO
FROM RESPONSE_TABLE
WHERE ROWNUM < 2;
You may find it even easier to use an XMLTable - necessary if an XML document has multiple Address nodes, but even with just one pulling the values out as columns is less repetitive, and it makes it easier to retrieve suitable data types:
select x.*
from response_table rt
cross join xmltable(
'/Response/Body/Status/Order/EndUser/Address'
passing xmltype(rt.response_clob)
columns sano number path 'SANO/#value',
sasn varchar2(30) path 'SASN/#value',
sath varchar2(10) path 'SATH/#value'
-- etc.
) x
where rownum < 2;
SANO SASN SATH
-------------------- ------------------------------ ----------
12345 Mickey Mouse Lane
Read more about using these functions to query XML data.

How to extract element-path from XMLType Node?

I would like to have a select statement on an XML document and one column should return me the path of each node.
For example, given the data
SELECT *
FROM TABLE(XMLSequence(
XMLTYPE('<?xml version="1.0"?>
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>').extract('/*//*[text()]'))) t;
Which results in
column_value
--------
<user><name>user1</name></user>
<user><name>user2</name></user>
<user><name>user3</name></user>
<user><name>user4</name></user>
I'd like to have a result like this:
path value
------------------------ --------------
/users/user/name user1
/users/user/name user2
/users/group/user/name user3
/users/user/name user4
I can not see how to get to this. I figure there are two thing that have to work together properly:
Can I extract the path from an XMLType with a single operation or method, or do I have to do this with string-magic?
What is the correct XPath expression so that I do get the whole element path (if thats possible), eg. <users><group><user><name>user3</name></user></group></user> insead of <user><name>user3</name></user>?
Maybe I am not understanding XMLType fully, yet. It could be I need a different approach, but I can not see it.
Sidenotes:
In the final version the XML document will be coming from CLOBs of a table, not a static document.
The path column can of course also use dots or whatever and the initial slash is not the issue, any representation would do.
Also I would not mind if every inner node also gets a result row (possibly with null as value), not only the ones with text() in it (which is what I am really interested in).
In the end I will need the tail element of path separate (always "name" in the example here, but this will vary later), i.e. ('/users/groups/user', 'name', 'user3'), I can deal with that separately.
You can achieve that with help of XMLTable function from Oracle XML DB XQuery function set:
select * from
XMLTable(
'
declare function local:path-to-node( $nodes as node()* ) as xs:string* {
$nodes/string-join(ancestor-or-self::*/name(.), ''/'')
};
for $i in $rdoc//name
return <ret><name_path>{local:path-to-node($i)}</name_path>{$i}</ret>
'
passing
XMLParse(content '
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>'
)
as "rdoc"
columns
name_path varchar2(4000) path '//ret/name_path',
name_value varchar2(4000) path '//ret/name'
)
For me XQuery looks at least more intuitive for XML data manipulation than XSLT.
You can find useful set of XQuery functions here.
Update 1
I suppose that you need totally plain dataset with full data at last stage.
This target can be reached by complicated way, constructed step-by-step below, but this variant is very resource-angry. I propose to review final target (selecting some specific records, count number of elements etc.) and after that simplify this solution or totally change it.
Update 2
All steps deleted from this Update except last because #A.B.Cade proposed more elegant solution in comments.
This solution provided in Update 3 section below.
Step 1 - Constructing dataset of id's with corresponding query results
Step 2 - Aggregating to single XML row
Step 3 - Finally get full plain dataset by querying constracted XML with XMLTable
with xmlsource as (
-- only for purpose to write long string only once
select '
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>' xml_string
from dual
),
xml_table as (
-- model of xmltable
select 10 id, xml_string xml_data from xmlsource union all
select 20 id, xml_string xml_data from xmlsource union all
select 30 id, xml_string xml_data from xmlsource
)
select *
from
XMLTable(
'
for $entry_user in $full_doc/full_list/list_entry/name_info
return <tuple>
<id>{data($entry_user/../#id_value)}</id>
<path>{$entry_user/name_path/text()}</path>
<name>{$entry_user/name_value/text()}</name>
</tuple>
'
passing (
select
XMLElement("full_list",
XMLAgg(
XMLElement("list_entry",
XMLAttributes(id as "id_value"),
XMLQuery(
'
declare function local:path-to-node( $nodes as node()* ) as xs:string* {
$nodes/string-join(ancestor-or-self::*/name(.), ''/'')
};(: function to construct path :)
for $i in $rdoc//name return <name_info><name_path>{local:path-to-node($i)}</name_path><name_value>{$i/text()}</name_value></name_info>
'
passing by value XMLParse(content xml_data) as "rdoc"
returning content
)
)
)
)
from xml_table
)
as "full_doc"
columns
id_val varchar2(4000) path '//tuple/id',
path_val varchar2(4000) path '//tuple/path',
name_val varchar2(4000) path '//tuple/name'
)
Update 3
As mentioned by #A.B.Cade in his comment, there are really simple way to join ID's with XQuery results.
Because I don't like external links in answers, code below represents his SQL fiddle, a little bit adapted to the data source from this answer:
with xmlsource as (
-- only for purpose to write long string only once
select '
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>' xml_string
from dual
),
xml_table as (
-- model of xmltable
select 10 id, xml_string xml_data from xmlsource union all
select 20 id, xml_string xml_data from xmlsource union all
select 30 id, xml_string xml_data from xmlsource
)
select xd.id, x.* from
xml_table xd,
XMLTable(
'declare function local:path-to-node( $nodes as node()* ) as xs:string* {$nodes/string-join(ancestor-or-self::*/name(.), ''/'') }; for $i in $rdoc//name return <ret><name_path>{local:path-to-node($i)}</name_path>{$i}</ret> '
passing
XMLParse(content xd.xml_data
)
as "rdoc"
columns
name_path varchar2(4000) path '//ret/name_path',
name_value varchar2(4000) path '//ret/name'
) x
This is not perfect but can be a start:
Here is a sqlfiddle
with xslt as (
select '<?xml version="1.0" ?><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<records>
<xsl:apply-templates/>
</records>
</xsl:template>
<xsl:template match="//name">
<columns>
<path>
<xsl:for-each select="ancestor-or-self::*">
<xsl:call-template name="print-step"/>
</xsl:for-each>
</path>
<value>
<xsl:value-of select="."/>
</value>
<xsl:apply-templates select="*"/>
</columns>
</xsl:template>
<xsl:template name="print-step">
<xsl:text>/</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="1+count(preceding-sibling::*)"/>
<xsl:text>]</xsl:text>
</xsl:template>
</xsl:stylesheet>'
xsl from dual)
, xmldata as
(select xmltransform(xmltype('<?xml version="1.0"?>
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>'), xmltype(xsl)) xd from xslt)
select XT.*
from xmldata c,
xmltable('$x//columns' passing c.xd
as "x"
columns
path_c VARCHAR2(4000) PATH 'path',
value_c VARCHAR2(4000) PATH 'value'
) as XT
This is what I tried to do:
Since you want the "path" I had to use xslt (credits to this post)
Then I used xmltransform for transforming your original xml with the xsl to the
desired output (path, value)
Then I used xmltable to read it as a table
This improves on above answer by A.B.Cade:
<xsl:template name="print-step">
<xsl:variable name="name" select="name()" />
<xsl:text>/</xsl:text>
<xsl:value-of select="$name"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="1+count(preceding-sibling::*[name()=$name])"/>
<xsl:text>]</xsl:text>
</xsl:template>
With result:
/users[1]/user[1]/name[1] user1
/users[1]/user[2]/name[1] user2
/users[1]/group[1]/user[1]/name[1] user3
/users[1]/user[3]/name[1] user4
Instead of:
/users[1]/user[1]/name[1] user1
/users[1]/user[2]/name[1] user2
/users[1]/group[3]/user[1]/name[1] user3
/users[1]/user[4]/name[1] user4