Sum of values extracted using SQL - 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

Related

Oracle XML query returns NULL

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

extract xml tag based on max xpath of list value

I'm attempting to extract a currency value from an xml list, which is at the same level as the maximum value of another tag (within the same list).
I am using Oracle SQL 11gR2.
The maximum value extract is based on the xpath max() function.
I've then attempted to subscript the list (for currency tag) based on the result of the max(), but the currency appears as NULL.
This is a small sample of the data, and the associated xpath's used:
with xml_data as (
select xmltype('<?xml version="1.0" encoding="ISO-8859-1"?>
<SOME_OBJECT xmlns="http://www.example.com/xxyy/">
<ILA>
<PRODUCT_LIST>
<PRODUCT>
<MAP_ENTRY>
<CURRENCY_ENTRY>EUR</CURRENCY_ENTRY>
</MAP_ENTRY>
<INITIAL_VALUE>1.4219777502E8</INITIAL_VALUE>
</PRODUCT>
<PRODUCT>
<MAP_ENTRY>
<CURRENCY_ENTRY>ZAR</CURRENCY_ENTRY>
</MAP_ENTRY>
<INITIAL_VALUE>1.4612991655E8</INITIAL_VALUE>
</PRODUCT>
<PRODUCT>
<MAP_ENTRY>
<CURRENCY_ENTRY>USD</CURRENCY_ENTRY>
</MAP_ENTRY>
<INITIAL_VALUE>1.4712991655E8</INITIAL_VALUE>
</PRODUCT>
</PRODUCT_LIST>
</ILA>
</SOME_OBJECT>') as msg from dual union all
select xmltype('<?xml version="1.0" encoding="ISO-8859-1"?>
<SOME_OBJECT xmlns="http://www.example.com/xxyy/">
<ILA>
<SUBSEQUENT_VALUE>10266</SUBSEQUENT_VALUE>
</ILA>
</SOME_OBJECT>') as msg from dual
)
--
select x.subsequent_value, x.max_initial_value ,x.currency
from xml_data d
,xmltable (xmlnamespaces(default 'http://www.example.com/xxyy/')
,'/SOME_OBJECT' passing d.msg
columns
max_initial_value number path 'max(ILA/PRODUCT_LIST/PRODUCT/INITIAL_VALUE)'
,currency varchar2(3) path 'ILA/PRODUCT_LIST/PRODUCT[INITIAL_VALUE=max(ILA/PRODUCT_LIST/PRODUCT/INITIAL_VALUE)]/MAP_ENTRY/CURRENCY_ENTRY'
,subsequent_value number path 'ILA/SUBSEQUENT_VALUE'
) as x;
So the existing output is:
SQL> #get_max
SUBSEQUENT_VALUE MAX_INITIAL_VALUE CUR
---------------- ----------------- ---
147129917
10266
The first line should include USD.
Any suggestions as to what the xpath should be ?
Your xpath looks fine to me in theory,
im not an expert but there might be a problem here :
your displayed value of MAX_INITIAL_VALUE is 147129917 so it has been rounded right ?
Therefore, it cannot be equal to the actual value of 1.4712991655E8 in your XML object
maybe if there is a round function :
currency varchar2(3) path 'ILA/PRODUCT_LIST/PRODUCT[INITIAL_VALUE=round(max(ILA/PRODUCT_LIST/PRODUCT/INITIAL_VALUE))]/MAP_ENTRY/CURRENCY_ENTRY'
it would probably take a second argument for which decimal to round at if it exists
The full path has to be specified for the max() function, as follows:
ILA/TION/PAWS/PRODUCT_LIST/PRODUCT[INITIAL_VALUE = max(/SOME_OBJECT/ILA/TION/PAWS/PRODUCT_LIST/PRODUCT/INITIAL_VALUE)]/MAP_ENTRY/CURRENCY_ENTRY'
Using the same data from the original post, with a modified SQL statement, that includes the full path shows up the currency:
select x.subsequent_value, x.max_initial_value ,x.currency
from xml_data d
,xmltable (xmlnamespaces(default 'http://www.example.com/xxyy/')
,'/SOME_OBJECT' passing d.msg
columns
p XMLTYPE PATH '.'
, max_initial_value number path 'max(ILA/TION/PAWS/PRODUCT_LIST/PRODUCT/INITIAL_VALUE)'
, currency varchar2(3) path 'ILA/TION/PAWS/PRODUCT_LIST/PRODUCT[INITIAL_VALUE = max(/SOME_OBJECT/ILA/TION/PAWS/PRODUCT_LIST/PRODUCT/INITIAL_VALUE)]/MAP_ENTRY/CURRENCY_ENTRY'
, subsequent_value number path 'ILA/TION/PAWS/SUBSEQUENT_VALUE'
) as x;
SUBSEQUENT_VALUE MAX_INITIAL_VALUE CURRENCY
---------------- ----------------- --------
147129917 USD
10266
Alternatively, playing around with virtual paths might also do the trick, but it will slow down your query.

Oracle 10.2.0.4.0 query on partial xpath

I need to change the below query to be able to query any kind of tender item.
/Basket/CardTenderItem/Description
/Basket/CashTenderItem/Description
So
/Basket/WildcardTenderItem/Description
I have looked at various examples on but cannot them to bring back any results when running (happily admit to user error if can get working!)
SELECT
RETURN_ID
,SALE_ID,
,extractValue(xmltype(RETURNxml),'/Basket/CashTenderItem/NetValue')
,extractValue(xmltype(RETURNxml),'/Basket/CashTenderItem/Description')
FROM SPR361
WHERE return_id = '9999.0303|20170327224954|2063'
If you only want to match anything the ends with TenderItem, but doesn't have anything after that, you could be specific with substring checks:
SELECT
RETURN_ID
,SALE_ID
,extractValue(xmltype(RETURNxml),
'/Basket/*[substring(name(), string-length(name()) - 9) = "TenderItem"]/NetValue')
,extractValue(xmltype(RETURNxml),
'/Basket/*[substring(name(), string-length(name()) - 9) = "TenderItem"]/Description')
FROM SPR361
WHERE return_id = '9999.0303|20170327224954|2063'
If you never have any nodes with anything after that fixed string then #Shnugo's contains approach is easier, and in Oracle would be very similar:
...
,extractValue(xmltype(RETURNxml),
'/Basket/*[contains(name(), "TenderItem")]/NetValue')
,extractValue(xmltype(RETURNxml),
'/Basket/*[contains(name(), "TenderItem")]/Description')
I'm not sure there's any real difference between name() and local-name() here.
If a basket can have multiple child nodes (card and cash, or more than one of each) you could also switch to XMLTable syntax:
SELECT
s.RETURN_ID
,s.SALE_ID
,x.netvalue
,x.description
FROM SPR361 s
CROSS JOIN XMLTable(
'/Basket/*[contains(name(), "TenderItem")]'
PASSING XMLType(s.RETURNxml)
COLUMNS netvalue NUMBER PATH './NetValue'
, description VARCHAR(80) PATh './Description'
) x
WHERE s.return_id = '9999.0303|20170327224954|2063'
And it's overkill here maybe, but for more complicated tests you can use other XPath syntax, like:
CROSS JOIN XMLTable(
'for $i in /Basket/*
where contains($i/name(), "TenderItem") return $i'
PASSING XMLType(s.RETURNxml)
...
This is SQL-Server syntax and I cannot test, if this works with Oracle too, but I think it will. You can use XQuery function contains():
DECLARE #xml XML=
N'<root>
<abcTenderItem>test1</abcTenderItem>
<SomeOther>should not show up</SomeOther>
<xyzTenderItem>test2</xyzTenderItem>
</root>';
SELECT #xml.query(N'/root/*[contains(local-name(),"TenderItem")]')
only the elements with "TenderItem" in their names show up:
<abcTenderItem>test1</abcTenderItem>
<xyzTenderItem>test2</xyzTenderItem>

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

Get multiple xml nodes (delimited)

I have a table with a xml that is formatted something like this (simplified for readability)
<parentItem xmlns:i="http://tempuri.org/1" xmlns="http://tempuri.org/2">
<ItemA></ItemA>
<ItemB></ItemB>
<ItemC xmlns:d2p1="http://tempuri.org/3">
<d2p1:string>value1</d2p1:string>
<d2p1:string>value2</d2p1:string>
<d2p1:string>value3</d2p1:string>
<!-- .... (0 to many strings here) -->
</ItemC>
</parentItem>
The only think I care about are the values in parentItem > ItemC > string
I would like to get those values delimited by something, such as a comma
Desired Result: "value1,value2,value3"
currently I can get one value by doing this:
SELECT CAST([QueryXml] as xml).value('(/*:parentItem/*:ItemC/node())[1]','nvarchar(max)')
FROM [opendb].[dbo].[MyTable]
Result: "value1"
I can also get all the values like this:
SELECT CAST([QueryXml] as xml).value('(/*:ConflictsSearchTermQuery/*:TermItems)[1]','nvarchar(max)')
FROM [opendb].[dbo].[ConflictsSearchTerms]
Result: "value1value2value3"
but I'm looking to get a delimited set of values
Desired Result: "value1,value2,value3"
To get multiple values out of XML you need to use the nodes() method of the XML data type.
However, since this method does not return a single, scalar value (but a rowset), you need to call it through CROSS APPLY.
WITH MyTable AS (
SELECT 1 AS ID, CAST('<parentItem xmlns:i="http://tempuri.org/1" xmlns="http://tempuri.org/2">
<ItemA></ItemA>
<ItemB></ItemB>
<ItemC xmlns:d2p1="http://tempuri.org/3">
<d2p1:string>value1</d2p1:string>
<d2p1:string>value2</d2p1:string>
<d2p1:string>value3</d2p1:string>
<!-- .... (0 to many strings here) -->
</ItemC>
</parentItem>' AS XML) AS QueryXml
)
SELECT
t.ID,
x.node.value('.', 'varchar(100)') AS nodeValue
FROM
MyTable t
CROSS APPLY QueryXml.nodes('
declare namespace i="http://tempuri.org/1";
declare namespace def="http://tempuri.org/2";
declare namespace d2p1="http://tempuri.org/3";
/def:parentItem/def:ItemC/d2p1:string'
) x(node)
gives you
ID nodeValue
----------- ------------------
1 value1
1 value2
1 value3
After that, if you really must, standard techniques for concatenating values in SQL Server apply.
Note that I have properly declared the namespaces in the XQuery instead of using *. Namespaces are important, don't ignore them.