getting name and value from xmltype attribute in oracle - sql

Description
Hello, I have a problem with extracting attribute names and values from XMLType value in Oracle.
Basically, I have a table, let's say TableA, which has a XMLType column, let's call it TableA_config. Values in TableA_config have structure like this:
<TableAConfig someAttribute1="value1" someAttribute2="value2" someAttribute3="value3" />. The number of attributes and their names may vary and are not known beforehand.
What I need to do is (for each row) create new XMLElement called TableAConfigList, which contains XMLElements called TableAConfig and each of those has two attributes: name and value. Now, number of TableAConfig nodes must be equal to number of attributes in TableA_config column, and each holds name of corresponding attribute in name attribute and its value in value attribute.
Example
From:
<TableAConfig someAttribute1="value1" someAttribute2="value2" someAttribute3="value3" />
I should get:
<TableAConfigList>
<TableAConfig name="someAttribute1" value="value1"/>
<TableAConfig name="someAttribute2" value="value2"/>
<TableAConfig name="someAttribute3" value="value3"/>
</TableAConfigList>
What I tried
I came up with idea to create a XMLTable from TableA_config column value and in it create two columns, which I can later select. It looks like this (it is a fragment of a bigger query):
SELECT XMLElement("TableAConfigList",
(SELECT
XMLAgg(
XMLElement("TableAConfig",
XMLAttributes(
tmp."attr_name" as "name",
tmp."attr_text" as "value"
)
)
) from XMLTable('/TableAConfig/#*'
passing TableA.TableA_config
columns
"attr_name" varchar(30) path 'name()',
"attr_text" varchar(30) path 'text()'
) tmp
)
) from dual
But now i get:
<TableAConfigList>
<TableAConfig name="someAttribute1"></TableAConfig>
<TableAConfig name="someAttribute2"></TableAConfig>
<TableAConfig name="someAttribute3"></TableAConfig>
</TableAConfigList>
There is no value. However, if I remove name from XMLAttributes it shows up. From:
SELECT XMLElement("TableAConfigList",
(SELECT
XMLAgg(
XMLElement("TableAConfig",
XMLAttributes(
tmp."attr_text" as "value"
)
)
) from XMLTable('/TableAConfig/#*'
passing TableA.TableA_config
columns
"attr_name" varchar(30) path 'name()',
"attr_text" varchar(30) path 'text()'
) tmp
)
) from dual
I get:
<TableAConfigList>
<TableAConfig value="value1"></TableAConfig>
<TableAConfig value="value2"></TableAConfig>
<TableAConfig value="value3"></TableAConfig>
</TableAConfigList>
I thought that maybe, for some reason, there can only be one attribute created this way, but if I add a new one by hardcoding it, it shows up in result, like this:
SELECT XMLElement("TableAConfigList",
(SELECT
XMLAgg(
XMLElement("TableAConfig",
XMLAttributes(
tmp."attr_text" as "value",
'testValue' as "testAttribute"
)
)
) from XMLTable('/TableAConfig/#*'
passing TableA.TableA_config
columns
"attr_name" varchar(30) path 'name()',
"attr_text" varchar(30) path 'text()'
) tmp
)
) from dual
Result:
<TableAConfigList>
<TableAConfig value="value1" testAttribute="testValue"></TableAConfig>
<TableAConfig value="value2" testAttribute="testValue"></TableAConfig>
<TableAConfig value="value3" testAttribute="testValue"></TableAConfig>
</TableAConfigList>
Putting in XMLAttributes both columns and hardcoded one gives me name and testAttribute, but no value.
Could someone tell me is it because I miss something terribly obvious, is it a bug or am I doing it completely wrong. I am pretty new to Oracle and PL/SQL and could really appreciate your help. Thanks!

You were almost there with your first attempt. While evaluating the XPath, when you are inside the attribute list with /TableAConfig/#*, you don't need text() to get the value of the attribute inside it. You are already at the attribute level so using just a "dot" . for current node would be sufficient.
So try something like this -
SELECT XMLElement("TableAConfigList",
(SELECT
XMLAgg(
XMLElement("TableAConfig",
XMLAttributes(
tmp."attr_name" as "name",
tmp."attr_text" as "value"
)
)
) from XMLTable('/TableAConfig/#*'
passing TableA.TableA_config
columns
"attr_name" varchar(30) path 'name()',
"attr_text" varchar(30) path '.'
) tmp
)
) from dual
The only difference from your first attempt is the xpath of the value attribute.

You can use XMLTRANSFORM too:
select xmltransform(
xmltype('<TableAConfig someAttribute1="value1" someAttribute2="value2" someAttribute3="value3" />'),
xmltype('<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="TableAConfig">
<TableAConfigList>
<xsl:for-each select="#*">
<xsl:element name="TableAConfig" >
<xsl:attribute name="name">
<xsl:value-of select="name()"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</TableAConfigList>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>'))
from dual

Related

XML PARSING ON ORACLE (HELP :/)

I have xml data like this;
<NctsYYSResponse xmlns="http://http://www.w3.org/2001/XMLSchema-instance">
<Root>
<Response xmlns="">
<RefID>11084672</RefID>
<Guid>b2231713</Guid>
<Durum>Thank You!</Durum>
</Response>
</Root>
</NctsYYSResponse>
I tried this sql codes but i could not get result;
1- SELECT EXTRACTVALUE (XMLType(xml_respond) , '/NctsYYSResponse/Root/Response/Guid') guid from table1
2-SELECT x.GUID,x.STATUS FROM table1,XMLTABLE(
'/NctsYYSResponse/Root/Response'
PASSING XMLTYPE(xml_respond)
COLUMNS
"GUID" VARCHAR2(20) PATH 'Guid',
"STATUS" VARCHAR2(100) PATH 'Durum') x ;
3-select x.GUID from table1, xmltable(
xmlnamespaces ('http://http://www.w3.org/2001/XMLSchema-instance' as "a" ,
default ''), '/NctsYYSResponse/Root/a:Response'
passing XMLType(xml_respond)
columns
"GUID" VARCHAR2(20) path 'Guid'
) x ;
Return null at all of them
If i change NctsYYSResponse 's type xmlns to xmlns:xsi , i can get result. But i need to get result when it is xmlns . Please help me :/
You were very close with your last attempt; you just applied the a namespace to the wrong elements:
select x.guid, x.status
from table1
cross apply xmltable (
xmlnamespaces (
'http://http://www.w3.org/2001/XMLSchema-instance' as "a",
default ''
),
'/a:NctsYYSResponse/a:Root/Response'
passing XMLType(xml_respond)
columns
guid VARCHAR2(20) path 'Guid',
status VARCHAR2(100) PATH 'Durum'
) x;
GUID STATUS
-------------------- ---------------
b2231713 Thank You!
db<>fiddle

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.

xsl string-join() multiple variables - only use non-empty

I'd like to create several xsl:variable that may or may not be null then join them:
<xsl:variable name="creatorType" select="replace(lib:merge(subfields/subfield[matches(#code,'[e]')],' '),'author|[.$]','')" />
<xsl:variable name="creatorAttribution" select="replace(lib:merge(subfields/subfield[matches(#code,'[j]')],' '),'[,-.]$','')" />
<xsl:variable name="creatorNameFullForm" select="replace(lib:merge(subfields/subfield[matches(#code,'[q]')],' '),'[,-()]$','')" />
<xsl:variable name="creatorAffiliation" select="replace(lib:merge(subfields/subfield[matches(#code,'[u]')],' '),'[,-.]$','')" />
string-join((xsl:sequence), 'delimiter') seems like a good fit but also joins variables with empty values.
<xsl:variable name="creatorDescriptors" select ="string-join(($creatorDates, $creatorType, $creatorAttribution, $creatorAffiliation),', ')"/>
How would you have it only string-join non-null xsl:variables?
I'm currently getting something like this:
Mozart, Wolfgang Amadeus (1756–1791, Composer, , )
Try this expression instead (replacing the variable names with your own variable names), so that only the non-empty nodes are selected in the sequence
<xsl:value-of select="string-join(($a, $b, $c, $d)[. != ''],', ')"/>

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

Generate XML file with Customized XML tags out of oracle database table

I am working on oracle database 9ir2
I need to convert some tables into xml files with custom format tags.
for example:
I want to generate XML from some columns in emp table then generate a file with name "myxmlfile.xml" as following:
<?xml version="1.0" encoding="UTF-8"?>
<entity-engine-xml>
<myxmlfile
EMPNO="8401"
ENAME="JHON"
HIREDATE="1988-12-30"
SAL="5000"
DEPTNO="10"
/>
<myxmlfile
...
/>
<myxmlfile
...
/>
</entity-engine-xml>
how to generate custom XML file with tags as above, and everytime the user need to do so, in other words "per user request" by using pl/sql, how to use oracle XML functions to output customized desired tags?.
how to convert xmltype to varchar2 ??? ... to_char() function is unable to convert XMLtype to char.
what is the easiest way to create XML file on client side ?
note: the user application is running on XP PCs, built by old oracle developer tools forms6i.
you do this with XMLELEMENT etc.
select xmlelement("entity-engine-xml",
xmlagg(
xmlelement(
"myxmlfile",
xmlattributes(empno as "EMPNO",
ename as "ENAME",
to_char(hiredate, 'yyyy-mm-dd') as "HIREDATE",
sal as "SAL",
deptno as "DEPTNO"
)
)
)
).getclobval()
from emp;
.
how to convert xmltype to varchar2 ???
theres a getStringVal function for this. i.e see in my example above i used getClobval. there's a getstringval() equivalent.
EDIT:
spooling:
set trims on feedback off heading off long 50000 linesize 32767 pagesize 0
col c format a32767
spool c:\temp\foo.xml
select xmlelement("entity-engine-xml",
xmlagg(
xmlelement(
"myxmlfile",
xmlattributes(empno as "EMPNO",
ename as "ENAME",
to_char(hiredate, 'yyyy-mm-dd') as "HIREDATE",
sal as "SAL",
deptno as "DEPTNO"
)
)
)
).transform(xmltype('<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>')) c
from emp;
spool off