Oracle XML query returns NULL - sql

I need to convert XML data to field/value pair but my query for the value is returning nulls
I have an XML type column in Oracle that I am trying to parse to get the field/value pair. I'm not sure if the structure of the XML is the cause but I cannot extract the value.
Here is a sample of the XML data:
<SaleFile BRANCH_ID="ABCZ7" name="FILE20190523_BN0016_MAIN.DAT" File_Type="MAIN" id="574455888ABCYY7" type="ACCOUNT">
<FIELD Type="ACCOUNT BALANCES AND CORRESPONDING DATES" Redact_ind="FALSE" Name="POSTED_PMT_AMT">-673.00</FIELD>
<FIELD Type="CONSUMER CONTACT INFORMATION" Redact_ind="FALSE" Name="SECONDARY CUSTOMER">N</FIELD>
<FIELD Type="CONSUMER IDENTIFYING INFORMATION" Redact_ind="FALSE" Name="SECONDARY CUSTOMER NAME"></FIELD>
</SaleFile>
I am trying to get the posted amount returned in my query.
SELECT xt.*
FROM SALES_DATA x,
XMLTABLE('/SaleFile/FIELD'
PASSING x.SELLER_DATA
COLUMNS
FIELD_VAL VARCHAR2(500) PATH 'FIELD'
) xt
The query runs without error but all I get back are NULLs.

Consider a different approach with xmlSequence and extractValue() with conditional in XPath expression:
select
extractValue(value(e), '//FIELD[#Type=''ACCOUNT BALANCES AND CORRESPONDING DATES'']') AS "amount"
from SALES_DATA x,
table (xmlSequence(extract(xml, '/SaleFile'))) e
Rextester Demo

Figured out by just trying some different codes:
SELECT xt.* FROM SALES_DATA x,
XMLTABLE('/SaleFile/FIELD'
PASSING x.Seller_DATA
COLUMNS
FIELD_VAL VARCHAR2(500) PATH '/' ) xt
this gave me the list of values for each FIELD node.

You have few FIELD elements, so a filter based on the Name="POSTED_PMT_AMT" attribute will be helpful.
SQL
SELECT xt.*
FROM SALES_DATA x,
XMLTABLE('/SaleFile/FIELD[#Name="POSTED_PMT_AMT"]'
PASSING x.SELLER_DATA
COLUMNS
PMT_AMT NUMERIC(10,2) PATH './text()'
) xt

Related

How Extract from XMLTYPE column, corresponding label for value in a tag within a value/label list from same XML

I got a sql from an Oracle table with a XMLTYPE column.
The Xml has a tag with a value '2' and in the same xml there is the list with value/label pairs like this:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<characteristics>
<motor>
<fuel>2</fuel>
</motor>
</characteristics>
<ValuesList>
<CarValues>
<CarValue>
<value>1</value>
<label>Diesel</label>
</CarValue>
<CarValue>
<value>2</value>
<label>Unleaded petrol</label>
</CarValue>
</CarValues>
</ValuesList>
</Root>
With extract() or extractValue() functions it's easy to get the value for tag with any of these staments a, b
SELECT extract(t.xmlColumn, '//fuel/text()').getStringVal() a,
extractValue(t.xmlColumn, '//fuel') b
FROM Table t
The problem is I want to get the label text for value '2' from Valueslist 'Unleaded petrol'
I try to get that value with a XPATH expresion like this:
extractValue(t.xmlColumn, '//CarValue/label[../value=//fuel]')
The XPATH has been evaluated with Notepad XML Tools and it works, but there's no way to get any result. it's always null.
Any idea how to achive this? I don't know how to use XMLTABLE or any other solution to this case.
You need to include the /text() part in your attempt:
SELECT
extract(t.xmlColumn,'//fuel/text()').getStringVal() a
,extractValue(t.xmlColumn,'//fuel') b
,extractValue(t.xmlColumn, '//CarValue/label[../value=//fuel/text()]') c
FROM your_table t
A
B
C
2
2
Unleaded petrol
In your version you're trying to match a label with a value of the node <fuel>2</fuel>, not the text within than node, 2.
Both extract() and extractValue() are deprecated, so you could use XMLQuery instead:
SELECT
XMLQuery('//fuel/text()' passing xmlColumn returning content) a
,XMLQuery('//CarValue/label[../value=//fuel/text()]/text()' passing xmlColumn returning content) a
FROM your_table t
A
A
2
Unleaded petrol
Or with XMLTable();
SELECT x.a, x.b
FROM your_table t
CROSS APPLY XMLTable(
'/'
passing t.xmlColumn
columns a number path '//fuel',
b varchar2(30) path '//CarValue/label[../value=//fuel/text()]'
) x
A
B
2
Unleaded petrol
fiddle
If your version of Oracle isn't working with the node traversal using ../ then you could do it the hard way by getting the fuel value and all the id/label pairs with separate XMLTable calls, and then filtering those that match:
SELECT x1.fuel, x2.label
FROM your_table t
CROSS JOIN XMLTable(
'//fuel'
passing t.xmlColumn
columns fuel number path '.'
) x1
JOIN XMLTable(
'//CarValue'
passing t.xmlColumn
columns value number path 'value',
label varchar2(30) path 'label'
) x2 ON x2.value = x1.fuel
FUEL
LABEL
2
Unleaded petrol
fiddle
All solutions Alex Poole gave are accepted solutions for the question case title where "a value/label list within same XML" is present.
Just for information, in case "a value/label list is not present in the same XML", solution is create an auxiliary XMLTable with a XMLTYPE field and the value/label list XML in it.
SELECT x1.fuel, x2.labelFROM your_table t
CROSS JOIN XMLTable(
'//fuel'
passing t.xmlColumn
columns fuel number path '.'
) x1
LEFT JOIN XMLTable(
'//CarValue'
passing
(SELECT XMLTYPE('
<ValuesList>
<CarValues>
<CarValue>
<value>1</value>
<label>Diesel</label>
</CarValue>
<CarValue>
<value>2</value>
<label>Unleaded petrol</label>
</CarValue>
</CarValues>
</ValuesList>
') FROM dual)
columns value number path 'value',
label varchar2(30) path 'label'
) x2 ON x2.value = x1.fuel

How to use XMLTABLE (Oracle SQL) in combination with attribute names

I'm querying an Oracle SQL table containing XML.
The (simplified) XML structure is as follows:
<aggregate type="HeadAggregate">
<entity type="Default" root="true" id="asdb7e9e-93324-43242d-b83a-f2d3202ed">
<attribute name="ObjectName" multivalue="false">ExampleName</attribute>
<attribute name="Subprocesses" multivalue="false">false</attribute>
<attribute name="ObjectDesc" multivalue="false">Description</attribute>
</entity>
<aggregate>
I want to get to retreive the object name. Therefore I wrote the following query:
SELECT xt.*
FROM DATABASENAME.TABLENAME x,
XMLTABLE('/aggregate/entity[#type = ''Default'']'
PASSING XMLTYPE(x.DATA)
COLUMNS
attribute_name VARCHAR2(100) PATH 'attribute[1]'
) xt
So far so good. This works fine! I get the desired output.
However, now I want to replace '[1]' in PATH by a reference to the attribute name to make my script a bit more flexible.
So I changed my script to:
SELECT xt.*
FROM DATABASENAME.TABLENAME x,
XMLTABLE('/aggregate/entity[#type = ''Default'']'
PASSING XMLTYPE(x.DATA)
COLUMNS
attribute_name VARCHAR2(100) PATH 'attribute[#name = ''ObjectName'']'
) xt
For some reason, now I get the following error message:
00907. 00000 - "missing right parenthesis"
*Cause:
*Action:
Error at Line: 3 Column: 47
I've been struggling on this for a while.
What am I doing wrong?
Many thanks in advance!
You can (or should) use double-quotes, rather than escaped single-quotes:
SELECT xt.*
FROM DATABASENAME.TABLENAME x,
XMLTABLE('/aggregate/entity[#type = "Default"]'
PASSING XMLTYPE(x.DATA)
COLUMNS
attribute_name VARCHAR2(100) PATH 'attribute[#name = "ObjectName"]'
) xt
db<>fiddle

Sum of values extracted using SQL

I have an xml like the below.
<LPNDetail>
<ItemName>5054807025389</ItemName>
<DistroNbr/>
<DistributionNbr>TR001000002514</DistributionNbr>
<OrderLine>2</OrderLine>
<RefField2/>
<RefField3>OU01180705</RefField3>
<RefField4>0002</RefField4>
<RefField5>Retail</RefField5>
<Qty>4</Qty>
<QtyUom>Unit</QtyUom>
</LPNDetail>
<LPNDetail>
<ItemName>5054807025563</ItemName>
<DistroNbr/>
<DistributionNbr>TR001000002514</DistributionNbr>
<OrderLine>4</OrderLine>
<RefField2/>
<RefField3>OU01180705</RefField3>
<RefField4>0004</RefField4>
<RefField5>Retail</RefField5>
<Qty>2</Qty>
<QtyUom>Unit</QtyUom>
</LPNDetail>
I have extracted the xml field using extract.xmltype and now i am getting the below result.
42
But i need to sum the quantity values i.e i need to get result as 6 (4+2).
Any help will be appreciated.
Thanks,
Shihaj
It is not clear what you mean by "an xml". If it's supposed to be an XML document, you are missing the outermost tags, perhaps something like <Document> ..... </Document>
If your text value is EXACTLY as you have shown it (which would be pretty bad), you can wrap within such outermost tags manually, and then use standard Oracle XML tools. For the illustration below I assume you simply have a string (VARCHAR2 or CLOB), not converted to XML type; in that case, I concatenate the beginning and end tags, and then convert to XMLtype, in the query.
with t ( str ) as (
select '<LPNDetail>
<ItemName>5054807025389</ItemName>
<DistroNbr/>
<DistributionNbr>TR001000002514</DistributionNbr>
<OrderLine>2</OrderLine>
<RefField2/>
<RefField3>OU01180705</RefField3>
<RefField4>0002</RefField4>
<RefField5>Retail</RefField5>
<Qty>4</Qty>
<QtyUom>Unit</QtyUom>
</LPNDetail>
<LPNDetail>
<ItemName>5054807025563</ItemName>
<DistroNbr/>
<DistributionNbr>TR001000002514</DistributionNbr>
<OrderLine>4</OrderLine>
<RefField2/>
<RefField3>OU01180705</RefField3>
<RefField4>0004</RefField4>
<RefField5>Retail</RefField5>
<Qty>2</Qty>
<QtyUom>Unit</QtyUom>
</LPNDetail>'
from dual
)
-- End of SIMULATED table (for testing purposes only, not part of the solution)
-- Query begins below this line
select sum(x.qty) as total_quantity
from t,
xmltable('/Document/LPNDetail'
passing xmltype('<Document>' || t.str || '</Document>')
columns qty number path 'Qty') x
;
Output:
TOTAL_QUANTITY
--------------
6

Getting XML out of a table created in SQL

I have created this table in Oracle SQL Developer
CREATE TABLE Test_T (
COL_1 VARCHAR(30),
COL_2 XMLTYPE
);
And have inserted this into it
INSERT INTO Test_T VALUES ('two', ('<?xml version="1.0" encoding="UTF-8"?>
<CATALOG>
<PLANT>
<COMMON>Bloodroot</COMMON>
<BOTANICAL>Sanguinaria canadensis</BOTANICAL>
<ZONE>4</ZONE>
<LIGHT>Mostly Shady</LIGHT>
<PRICE>$2.44</PRICE>
<AVAILABILITY>031599</AVAILABILITY>
</PLANT>
<PLANT>
<COMMON>Columbine</COMMON>
<BOTANICAL>Aquilegia canadensis</BOTANICAL>
<ZONE>3</ZONE>
<LIGHT>Mostly Shady</LIGHT>
<PRICE>$9.37</PRICE>
<AVAILABILITY>030699</AVAILABILITY>
</PLANT></CATALOG>'));
My goal is to return the <COMMON> Name </COMMON> ONLY WHERE the zone is 3 or LESS. So this should return Columbine.
I thought about using XMLExists Im not too familiar with XML so this is what I had so far.
SELECT COL_2 FROM Test_T WHERE XMLExists('//ZONE[COL_2 <= 3]' PASSING BY REF COL_2);
I'm not sure if I am accessing the ZONE right.
Could anyone guide me in the right direction?
Try the below select query :
SELECT COMMON_NAME FROM Test_T WHERE XMLExists( 'CATALOG/PLANT[ZONE<=3]/COMMON[text()]' PASSING COMMON_NAME )
The problem is with your path( '//ZONE[COL_2 <= 3]' ). COL_2 is not a valid XML node, it's just the name of your column.
The proper path would be //ZONE[text() <= 3].
text() is a special node reference that tells oracle to grab the text inside the ZONE node <ZONE>THIS TEXT</ZONE>. You can only target nodes in your actual XML schema.
Also, be aware that the path is CASE SENSITIVE to what's in your XML. Remembering that will save you time.
Additionally, another way to write your select would be this. In this example, Oracle does an implicit join and returns a row for each //PLANT with /ZONE/text() <= 3. The path in the XMLSEQUENCE is important here because it determines how oracle splits up each row, meaning that you can't just target //ZONE because you would only get a row for each ZONE rather than a row for each PLANT.
In the select clause, you can extract individual node values for each PLANT if you have more than one.
SELECT VALUE(P) --THE FULL XML FOR EACH PLANT
VALUE(P).EXTRACT('//COMMON/text()').getstringval() AS COMMON, --INDIVIDUAL NODE VALUE
VALUE(P).EXTRACT('//BOTANICAL/text()').getstringval() AS BOTANICAL --INDIVIDUAL NODE VALUE
FROM Test_T, TABLE(XMLSEQUENCE(EXTRACT(COL_2, '//PLANT[ZONE<= 3]'))) p

Issue with creating XMLQuery for given XPATH

I have a table having one columns as XMLTYPE being stored with Object-Relational storage. Below is table ddl.
CREATE TABLE Orders ( Order_id number not null,
Order_status Varchar2(100),
Order_desc XMLType not null)
XMLTYPE Order_desc STORE AS OBJECT RELATIONAL
XMLSCHEMA "http://localhost/public/xsd/Orderstore.xsd"
ELEMENT "OrderVal"
);
I have successfully registered the schema to load XSD with XML DB. Below is the XML being loaded into the XMLTYPE column.
<?xml version="1.0" encoding="utf-8" ?>
<draftorders>
<OrderSumm>
<Ordercod>OrderBookings</Ordercod>
</OrderSumm>
<Orderattrs>
<Orderattr Ordername="HROrder">
<OrderVals>
<OrderVal>
<listvalue>Order1</listvalue>
<Orderattrs>
<Orderattr Ordername="Node1_Child1">
<OrderVals>
<OrderVal>
<listvalue><![CDATA[ Node1_Child1_OrderValue_1]]></listvalue>
<Orderattrs>
<Orderattr Ordername="Node2_Child1">
<OrderVals>
<OrderVal>
<listvalue><![CDATA[ Node2_Child1_OrderValue_1]]></listvalue>
</OrderVal>
</OrderVals>
</Orderattr>
<Orderattr Ordername="Node2_Child2">
<OrderVals>
<OrderVal>
<listvalue><![CDATA[ Node2_Child2_OrderValue_1]]></listvalue>
</OrderVal>
</OrderVals>
</Orderattr>
</Orderattrs>
</OrderVal>
</OrderVals>
</Orderattr>
</Orderattrs>
</OrderVal>
</OrderVals>
</Orderattr>
</Orderattrs>
</draftorders>
I have the query using "extract" to print the below output:
SELECT extract(o.Order_desc,'/OrderVal[1]/Orderattrs/Orderattr[1]/OrderVals/OrderVal[1]/Orderattrs/Orderattr[0]/#Ordername').getStringVal() "Node1",
extract(o.Order_desc,'/OrderVal[1]/Orderattrs/Orderattr[1]/OrderVals/OrderVal[1]/Orderattrs/Orderattr[0]/OrderVals/OrderVal[1]/listvalue/text()').getStringVal() "Node1Child",
extract(o.Order_desc,'/OrderVal[1]/Orderattrs/Orderattr[1]/OrderVals/OrderVal[1]/Orderattrs/Orderattr[1]/#Ordername').getStringVal() "Node2",
extract(c.Order_desc,'/OrderVal[1]/Orderattrs/Orderattr[1]/OrderVals/OrderVal[1]/Orderattrs/Orderattr[1]/OrderVals/OrderVal[1]/listvalue/text()').getStringVal() "Node2Child"
FROM Orders o;
OUTPUT:-
Node2_Child1
Node2_Child1_OrderValue_1
Node2_Child2
Node2_Child2_OrderValue_1
I want to achieve the same output using XMLQuery, but I am unable to build query to print the child node. Till now, I can only print the node value using XMLQuery as given below:-
SELECT XMLQuery( '/OrderVal[1]/Orderattrs/Orderattr[1]/OrderVals/OrderVal[1]/Orderattrs/Orderattr[0]/#Ordername'
PASSING o.Order_desc RETURNING CONTENT
)
FROM Orders o;
How can I achieve the same output from using "extract", with "XMLQuery" ?
Thanks.
/********
Modified query run:-
SELECT XMLQuery('//OrderVal/Orderattrs/Orderattr/(#Ordername, OrderVals/OrderVal/listvalue)/data(.)'
PASSING o.Order_desc RETURNING CONTENT
)
FROM Orders o;
Output:-
Node2_Child1
Node2_Child1_OrderValue_1
Node2_Child
Fetching all Nodes and its child's using XMLTABLE.
SELECT ord.OrdName, ord.OrdVal
FROM Orders, XMLTable('/OrderVal[1]/Orderattrs/Orderattr[1]/OrderVals/OrderVal[1]/Orderattrs/Orderattr'
PASSING Order_desc
COLUMNS "OrdName" VARCHAR2(4000) PATH '#Ordername',
"OrdVal" VARCHAR2(4000) PATH 'OrderVals/OrderVal[1]/listvalue') ord;
Output:-
Node2_Child1
Node2_Child1_OrderValue_1
Node2_Child2
Node2_Child2_OrderValue_1
......
Node2_Child2500
Node2_Child2500_OrderValue_1
How can I achieve the same using XMLQuery ??
You queries and example XML do not seem to match, so I aligned to your queries as far as possible. As your XML was broken, too you might have to adjust some axis steps.
You can "branch" at some point (for example to fetch the attribute value and a subnode) using parentheses. This query will fetch all results at once for an arbitrary number of order attributes:
//OrderVal/Orderattrs/Orderattr/(#Ordername, OrderVals/OrderVal/listvalue)/data(.)