Oracle XMLTABLE and XPATH - sql

I'm a bit lost with Xpath, I'm trying to get attributes from a XML, I'm currently using :
XMLTABLE('/attrs/attr[#name="ImagesWEB"]/string'
PASSING XMLTYPE(XML)
COLUMNS IMAGESWEB VARCHAR2(100) PATH '.'
)
But I need to get an other value with attribute name Image2, so I did an other XMLTABLE but I guess it's not the right way to do it, I have to use XMLTABLE('/attrs/attr'... with two columns, but don't know how can I get both (Image2 can be missing form the XML data) form XPATH ?
Here an XML example (coming for my product's CLOB) :
<attrs>
<attr multiple="true" name="Image2">
<string>IMG2.PNG</string>
<string>IMG3.PNG</string>
<string>IMG4</string>
</attr>
<attr multiple="true" name="ImagesWEB">
<string>IMG.PNG</string>
</attr>
<attr name="ShortLink">
<string>/PRODUCT.html</string>
</attr>
<attr name="TITRE">
<string>TITLE</string>
</attr>
<attr name="name">
<string>PRODUCT</string>
</attr>
</attrs>
Thansk!

You can change your main XPath to just get the attrs, and then filter which you want in the Xpath for each columns clause:
XMLTABLE('/attrs'
PASSING XMLTYPE(XML)
COLUMNS
IMAGESWEB VARCHAR2(100) PATH 'attr[#name="ImagesWEB"]/string',
IMAGE2 VARCHAR2(100) PATH 'attr[#name="Image2"]/string'
)
db<>fiddle with some made-up data.
But what if I have two values (it might happen) ?
If you can have multiple string nodes under an attribute then you can get both attr nodes from one XMLTable call, and then have two more - one for each attr - to get the strings:
SELECT t.id, x2.imagesweb, x3.image2
FROM your_table t
CROSS APPLY
XMLTABLE('/attrs'
PASSING XMLTYPE(t.XML)
COLUMNS
IMAGESWEB XMLTYPE PATH 'attr[#name="ImagesWEB"]',
IMAGE2 XMLTYPE PATH 'attr[#name="Image2"]'
) x1
OUTER APPLY
XMLTABLE('/attr/string'
PASSING x1.imagesweb
COLUMNS
IMAGESWEB VARCHAR2(100) PATH '.'
) x2
OUTER APPLY
XMLTABLE('/attr/string'
PASSING x1.image2
COLUMNS
IMAGE2 VARCHAR2(100) PATH '.'
) x3
ID IMAGESWEB IMAGE2
-- --------- ----------
1 ABC DEF
2 ABC
3 DEF
3 DEF2
4 IMG.PNG IMG2.PNG
4 IMG.PNG IMG3.PNG
4 IMG.PNG IMG4
db<>fiddle with mix of made-up and sample data.
This uses cross apply and outer apply (because one or the other attribute might not exist), which are available from Oracle 12c (12.1.0.1).

Related

exporting xml value from CLOB data field in oracle using multiple namespace

I have a table which contains x number of records. One of the fields is a CLOB and contains XML with a particular field
Here is a very shortened version of the XML
<metadata xml:lang="en"
xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:srv="http://www.isotc211.org/2005/srv" xmlns:gts="http://www.isotc211.org/2005/gts" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<gmd:GEMINI_Metadata>
<gmd:fileIdentifier>
<gco:CharacterString>cf40a39a-0721-4fd4-84f3-adc28aee1158</gco:CharacterString>
</gmd:fileIdentifier>
<gmd:dateStamp>
<gco:Date>2019-01-16</gco:Date>
</gmd:dateStamp>
</gmd:GEMINI_Metadata>
</metadata>
What I would like to do is get characterstring value from the fileIdentifier tag using SQL
I have tried the following
select EXTRACT (XMLType (DOCUMENTATION), '//fileIdentifier//gco:CharacterString','xmlns:gmd="http://www.isotc211.org/2005/gmd"', 'xmlns:gco="http://www.isotc211.org/2005/gco"') as DOCUMENTATION from sde.gdb_items_vw where name = 'testTable'
i get the following
ORA-00939: too many arguments for function
If I try only specifying one tag and one namespace like this
select EXTRACT (XMLType (DOCUMENTATION), '//gmd:fileIdentifier','xmlns:gmd="http://www.isotc211.org/2005/gmd"') as DOCUMENTATION from sde.gdb_items_vw where name = 'testTable';
I get the following
DOCUMENTATION
--------------------------------------------------------------------------------
<gmd:fileIdentifier xmlns:gmd="http://www.isotc211.org/2005/gmd"><gco:CharacterS
So what is the correct way of getting a particular tag that has multiple namespaces within its tree?
The best method is to use XMLTABLE. Here you can easily specify the namespaces.
SELECT doc AS documentation
FROM sde.gdb_items_vw,
XMLTABLE( xmlnamespaces( 'http://www.isotc211.org/2005/gmd' AS "gmd",
'http://www.w3.org/2001/XMLSchema-instance' AS "xsi",
'http://www.isotc211.org/2005/gco' AS "gco",
'http://www.isotc211.org/2005/srv' AS "srv",
'http://www.isotc211.org/2005/gts' AS "gts",
'http://www.opengis.net/gml' AS "gml",
'http://www.w3.org/1999/xlink' AS "xlink",
'urn:schemas-microsoft-com:xslt' AS "msxsl"
),
'/metadata' PASSING XMLTYPE(documentation)
COLUMNS doc VARCHAR2(1000) PATH 'gmd:fileIdentifier/gco:CharacterString'
);
Result:
DOCUMENTATION
------------------------------------
cf40a39a-0721-4fd4-84f3-adc28aee1158

How to extract value of all the child nodes of a specific node (provided through input parameter) from XMLType Column in Oracle

One of our requirements is to get the value of all the child nodes of a given specific XML node.
I have got a solution for this using Microsoft SQL Server but I need the same in Oracle. Please see the below query.
Note: incase if there is more than one child node, the result should be the concatenation of all the individual child nodes'value.
select
REPLACE(Properties, 'utf-8', 'utf-16'),
CAST(REPLACE(Col1, 'utf-8', 'utf-16') as XML).value('(//*[local-name() = sql:variable("#var2")])[1]', 'varchar(200)')
from A
Following is a sample data/row from Col1:
<?xml version="1.0" encoding="utf-8"?><ConstantInputProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Value xsi:type="xsd:int">0</Value></ConstantInputProperties>
here A is the table and Col1 is one of the columns of table A.
I tried to convert below Solution but it gives me the XML instead of values.
SELECT col1,
EXTRACT(XMLTYPE(col1), '(/*[local-name()="ConstantInputProperties"][1])')
FROM A
Example:
<?xml version="1.0" encoding="utf-8"?><ConstantInputProperties xmlns:xsd="w3.org/2001/XMLSchema" xmlns:xsi="w3.org/2001/XMLSchema-instance"><Value xsi:type="ArrayOfInt"><int>0</int><int>1</int></Value></ConstantInputProperties>
Expected Output 01
<?xml version="1.0" encoding="utf-8"?><ConstantInputProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Value xsi:type="ArrayOfBoolean"><boolean>true</boolean><boolean>true</boolean><boolean>true</boolean><boolean>true</boolean><boolean>true</boolean></Value></ConstantInputProperties>
Expected Output truetruetruetruetrue
<?xml version="1.0" encoding="utf-8"?><ConstantInputProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Value xsi:type="ArrayOfDecimal"><decimal>1.0000000000</decimal></Value></ConstantInputProperties>
Expected Output 1.0000000000
You could manipulate the XML inside an XPath with FLWOR syntax, but you could also use XMLTable to extract all the values; or rather, two XMLTables, one for a singleton element type and a second optional one for array expansion; get all the values as strings; and aggregate the results together:
select a.id,
listagg(coalesce(x1.value, x2.value), ' ')
within group (order by coalesce(x1.n, x2.n)) as result
from a
cross apply xmltable (
'(/*[local-name()=$var1][1])'
passing xmltype(col1), 'ConstantInputProperties' as "var1"
columns
n for ordinality,
value varchar2(30) path 'Value[#xsi:type="xsd:int"]',
array xmltype path 'Value[fn:starts-with(#xsi:type, "ArrayOf")]'
) x1
outer apply xmltable (
'Value/*'
passing array
columns
n for ordinality,
value varchar2(30) path '.'
) x2
group by a.id;
ID | RESULT
-: | :-----------------------
1 | 0
2 | 0 1
3 | true true true true true
4 | 1.0000000000
db<>fiddle
The n for ordinality just gives a numeric value that lets you keep the original sub-element order when aggregating (so you get 0 1 and not 1 0), If you don't want a spaces added to to the aggregated value then just change the second listagg argument from ' ' to null, though then you can't sell the difference between a singleton 10 and a pair of values with 1 and 0, so that doesn't seem very useful - not that an aggregated value seems that useful anyway really.
You could split into multiple sub-XMLTables, but that's probably not going to gain you anything here; db<>fiddle for info though.
can you suggest how to pass the ConstantInputProperties value as an argument and use it as a variable in function input in this case EXTRACT(XMLTYPE(col1), '(/[local-name()="ConstantInputProperties"]//text())')
The extract() function is deprecated. Use XMLQuery instead; for example:
select xmlquery(
'(/*[local-name()=$var1][1])/Value/text()'
passing xmltype(col1), 'ConstantInputProperties' as "var1"
returning content)
from a
Few examples:
with a as (
select q'[<?xml version="1.0" encoding="utf-8"?><ConstantInputProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Value xsi:type="xsd:int">0</Value></ConstantInputProperties>
]' col1 from dual
)
select
x.*
from
a,
xmltable(
'//*[local-name()="ConstantInputProperties"][1]'
passing xmltype(a.col1)
columns
res xmltype path '.'
) x;
--Result:
RES
------------------------------------------------------------------------------
<ConstantInputProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Value xsi:type="xsd:int">0</Value></ConstantInputProperties>
with a as (
select q'[<?xml version="1.0" encoding="utf-8"?><ConstantInputProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Value xsi:type="xsd:int">0</Value></ConstantInputProperties>
]' col1 from dual
)
select
x.*
from
a,
xmltable(
'//*[local-name()="ConstantInputProperties"]/*/text()'
passing xmltype(a.col1)
columns
res xmltype path '.'
) x;
--Result:
RES
--------------------------------------------------------------
<Value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:int">0</Value>

Reading XML Namespace using Oracle SQL

My XML Looks like below
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<wfm:Statement xmlns:wfm="http://example.org/sample/xsd/sampleStatement/2013/05" xmlns:wfmMerchant="http://www.eds.com/sample/xsd/wfmMerchant/2012/03"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<wfm:StatementParameters>
<wfmMerchant:HierarchyCd>012-12-002-107-050</wfmMerchant:HierarchyCd>
</wfm:StatementParameters>
<StatementAmount>27.140</StatementAmount>
</wfm:Statement>
I am trying to get the value of StatementAmount tag using Oracle query like below
select MS.MERCHANT,MS.CHAIN_HIERARCHY_CD,MS.CYCLE_DATE, X.StatementAmount
FROM CHAIN_STATMNT_HIST_XML MS
CROSS JOIN XMLTABLE(XMLNAMESPACES('http://example.org/sample/xsd/sampleStatement/2013/05' AS "wfm", 'http://www.eds.com/sample/xsd/wfmMerchant/2012/03' as wfmmerchant
default 'http://www.w3.org/2001/XMLSchema-instance')
,'/wfm:Statement/StatementAmount' passing xmltype(MS.XML_REPORT)
columns StatementAmount varchar(18) path '.')X
But, I am always getting NULL. I can able to successfully retrieve Hierarchy value from the XML which has namespace. But StatementAmount tag doesn't have any namespace and I have trouble retrieving it.
Can someone help with this issue ?
Your default namespace declaration seems to be causing the problem; without that (and ignoring wfmMerchant):
-- CTE for sample data
with CHAIN_STATMNT_HIST_XML (merchant, chain_hierarchy_cd, cycle_date, XML_REPORT) as (
select 1, 2, sysdate, '<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<wfm:Statement xmlns:wfm="http://example.org/sample/xsd/sampleStatement/2013/05" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<wfm:StatementParameters>
<!-- excluding this as namespace not provided -->
<!-- <wfmMerchant:HierarchyCd>012-12-002-107-050</wfmMerchant:HierarchyCd> -->
</wfm:StatementParameters>
<StatementAmount>27.140</StatementAmount>
</wfm:Statement>' from dual
)
-- actual query
select MS.MERCHANT,MS.CHAIN_HIERARCHY_CD,MS.CYCLE_DATE, X.StatementAmount
FROM CHAIN_STATMNT_HIST_XML MS
CROSS JOIN XMLTABLE(
XMLNAMESPACES('http://example.org/sample/xsd/sampleStatement/2013/05' AS "wfm"),
'/wfm:Statement/StatementAmount' passing xmltype(MS.XML_REPORT)
columns StatementAmount varchar(18) path '.'
) X
/
MERCHANT CHAIN_HIERARCHY_CD CYCLE_DATE STATEMENTAMOUNT
---------- ------------------ ---------- ------------------
1 2 2018-09-04 27.140
I'm not sure why you would use varchar2(18) as the datatype rather than number; and if there is only one statement amount per statement you could do:
select MS.MERCHANT,MS.CHAIN_HIERARCHY_CD,MS.CYCLE_DATE, X.StatementAmount
FROM CHAIN_STATMNT_HIST_XML MS
CROSS JOIN XMLTABLE(
XMLNAMESPACES('http://example.org/sample/xsd/sampleStatement/2013/05' AS "wfm"),
'/wfm:Statement' passing xmltype(MS.XML_REPORT)
columns StatementAmount number path 'StatementAmount'
) X

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.

Extracting a node where xmlns is set to blank

I'm having difficulties extracting the value from certain nodes in an XML structure using XMLTABLE. Below query works perfectly when you remove the xmlns="" attribute from the SubListItem nodes. And as you can see, the XML already has a default namespace. I honestly have no clue how I can deal with this "blanking out" of the namespace on certain nodes like this.
For further clarification, the creation of this XML is not within my control and is provided by a third-party. I've also changed the names of the nodes and the content from the delivered files while preserving the structure of the XML.
SELECT f.airline, f.flightnumber, fl.gate
FROM xmltable(
xmlnamespaces(
default 'http://some/name.space',
'http://www.w3.org/2001/XMLSchema' as "xsd",
'http://www.w3.org/2001/XMLSchema-instance' as "xsi"
),
'Body/Flight'
passing xmltype(
'<?xml version="1.0" encoding="utf-16"?>
<Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://some/name.space">
<Sender>
<System>ConnectionManagement</System>
</Sender>
<Flight>
<Airline>ABC</Airline>
<Number>1234</Number>
<SubList>
<SubListItem xmlns="">
<Gate>X</Gate>
</SubListItem>
<SubListItem xmlns="">
<Gate>Y</Gate>
</SubListItem>
<SubListItem xmlns="">
<Gate>Z</Gate>
</SubListItem>
</SubList>
</Flight>
</Body>'
)
columns airline varchar2(100) path 'Airline'
, flightNumber VARCHAR2(5) path 'Number'
, subList XMLTYPE path 'SubList'
) f
, xmltable (
xmlnamespaces( default 'http://some/name.space'),
'/SubList/SubListItem'
passing f.subList
columns gate varchar2(5) path 'Gate'
) fl
;
How can I target the Gate node when the XML looks like this?
Leave the default namespace alone in the second XMLTable, and specify a named namespace for the path you do have:
...
, xmltable (
xmlnamespaces( 'http://some/name.space' as "ns"),
'/ns:SubList/SubListItem'
passing f.subList
columns gate varchar2(5) path 'Gate'
) fl
;
AIRLINE FLIGH GATE
---------- ----- -----
ABC 1234 X
ABC 1234 Y
ABC 1234 Z
The SubList still has to match that, but as the child nodes don't the default is incorrect the way you have it. If you remove the xmlns="" as you mentioned in the question then that inherits the namespace from its parent, so your default works. With that override to no-namespace you can't use a default.