Extract specific xml node from duplicate ones in oracle - sql

Given is the value of "my_xml" column in "XYZ" table
<?xml version="1.0" encoding="UTF-8"?>
<India>
<city>
<string>ADI</string>
<string>Ahmedabad</string>
</city>
<city>
<string>GNR</string>
<string>Gandhinagar</string>
</city>
<city>
<string>PUN</string>
<string>Pune</string>
</city>
<city>
<string>RJT</string>
<string>Rajkot</string>
</city>
</India>
I am trying to extract value of second string node where first string node value is ADI
Output should be "Ahmedabad" only
Failed attempts:
select t.my_xml.extract('/India/city/string[2]/text()').getStringVal() from XYZ t where t.my_xml.existsNode('/India/city[string[1] = "ADI"]') = 1;
Output for above query is AhmedabadGandhinagarPuneRajkot
Expected output: Ahmedabad
How to extract specific node value for string node here?

You want to select the node that has the ADI text as first string.
Try this:
select
t.my_xml.extract('//city[./string[1]/text() = "ADI"]/string[2]/text()').getStringVal()
from XYZ t
where t.my_xml.existsNode('/India/city[string[1] = "ADI"]') = 1;

Use XMLTable to extract the values:
SELECT t.*
FROM XYZ x,
XMLTable(
'/India/city'
PASSING x.my_xml
COLUMNS string1 CHAR(3) PATH './string[1]',
string2 VARCHAR2(20) PATH './string[2]'
) t
WHERE t.string1 = 'ADI';

1) With xmlquery and flwor xquery
select xmlcast( xmlquery('for $i in ./India/city where $i//string[1]/text() = $cond return $i/string[2]/text()' passing xmltype(q'~<India>
<city>
<string>ADI</string>
<string>Ahmedabad</string>
</city>
<city>
<string>GNR</string>
<string>Gandhinagar</string>
</city>
<city>
<string>PUN</string>
<string>Pune</string>
</city>
<city>
<string>RJT</string>
<string>Rajkot</string>
</city>
</India>~'), 'ADI' as "cond" returning content) as varchar2(20)) result from dual;
2) If you're expecting more than one row try xmltable.
select * from xmltable('$doc/India/city' passing xmltype(q'~<India>
<city>
<string>ADI</string>
<string>Ahmedabad</string>
</city>
<city>
<string>GNR</string>
<string>Gandhinagar</string>
</city>
<city>
<string>PUN</string>
<string>Pune</string>
</city>
<city>
<string>ADI</string>
<string>Ahmedabad</string>
</city>
<city>
<string>RJT</string>
<string>Rajkot</string>
</city>
</India>~') as "doc"
columns
string1 varchar2(20) path'./string[1]/text()'
, string2 varchar2(20) path'./string[2]/text()'
)
where string1='ADI'
and xmltable with flwor xquery
select column_value from xmltable('for $el in $doc/India/city where $el//string[1]/text() = "ADI" return $el/string[2]/text()' passing xmltype(q'~<India>
<city>
<string>ADI</string>
<string>Ahmedabad</string>
</city>
<city>
<string>GNR</string>
<string>Gandhinagar</string>
</city>
<city>
<string>PUN</string>
<string>Pune</string>
</city>
<city>
<string>ADI</string>
<string>Ahmedabad</string>
</city>
<city>
<string>RJT</string>
<string>Rajkot</string>
</city>
</India>~') as "doc" )
;

Related

Sql return results as xml

Given this data:
FName Lname ApartmentNumber
-----------------------------------
David Shumer 1
John Deer 1
Mark Ratz 2
Steven Styer 2
I would like to return it as xml, so I did this:
select * from Table1
for xml raw('person'), root('PeopleInApartment'), elements
And I got this result
<PeopleInApartment>
<Person>
<FName>David</FName>
<LName>Shumer</LName>
<ApartmentNumber>1</ApartmentNumber>
</Person>
<Person>
<FName>John</FName>
<LName>Deer</LName>
<ApartmentNumber>1</ApartmentNumber>
</Person>
<Person>
<FName>Mark</FName>
<LName>Ratz</LName>
<ApartmentNumber>2</ApartmentNumber>
</Person>
<Person>
<FName>Steven</FName>
<LName>Styer</LName>
<ApartmentNumber>2</ApartmentNumber>
</Person>
</PeopleInApartment>
Is it possible to group people by apartment so the result would look like this?:
<Apartments>
<PeopleInApartment>
<Person>
<FName>David</FName>
<LName>Shumer</LName>
<ApartmentNumber>1</ApartmentNumber>
</Person>
<Person>
<FName>John</FName>
<LName>Deer</LName>
<ApartmentNumber>1</ApartmentNumber>
</Person>
</PeopleInApartment>
<PeopleInApartment>
<Person>
<FName>Mark</FName>
<LName>Ratz</LName>
<ApartmentNumber>2</ApartmentNumber>
</Person>
<Person>
<FName>Steven</FName>
<LName>Styer</LName>
<ApartmentNumber>2</ApartmentNumber>
</Person>
</PeopleInApartment>
</Apartments>
Sql solution. Note subquery must depend only on outer columns in the GROUP BY, t1.ApartmentNumber specifically.
select t1.ApartmentNumber
, (select t2.FName, t2.Lname
from Table1 t2
where t2.ApartmentNumber = t1.ApartmentNumber
for xml path('Person'), type
) as PeopleInApartment
from Table1 t1
group by ApartmentNumber
for xml path('Apartment'), root('Apartments'), elements
Returns data apartment - wise
<Apartments>
<Apartment>
<ApartmentNumber>1</ApartmentNumber>
<PeopleInApartment>
<Person>
<FName>David </FName>
<Lname>Shumer</Lname>
</Person>
<Person>
<FName>John </FName>
<Lname>Deer </Lname>
</Person>
</PeopleInApartment>
</Apartment>
<Apartment>
<ApartmentNumber>2</ApartmentNumber>
<PeopleInApartment>
<Person>
<FName>Mark </FName>
<Lname>Ratz </Lname>
</Person>
<Person>
<FName>Steven</FName>
<Lname>Styer </Lname>
</Person>
</PeopleInApartment>
</Apartment>
</Apartments>
Use xml linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication3
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XElement apartments = new XElement("Apartments");
var groups = doc.Descendants("Person")
.GroupBy(x => (int)x.Element("ApartmentNumber"))
.ToList();
foreach (var group in groups)
{
XElement peopleInApartment = new XElement("PeopleInApartment", group);
apartments.Add(peopleInApartment);
}
}
}
}
Please check Below :
select
(select t2.FName, t2.Lname, t1.ApartmentNumber
from #TEMP t2
where t2.ApartmentNumber = t1.ApartmentNumber
for xml path('Person'), type
) as PeopleInApartment
from #TEMP t1
group by ApartmentNumber
for xml path(''), root ('Apartments')

XQuery Nested Query

I can't figure out how to execute this query:
Using XQuery,
For each rep number, output:
A. firstname, lastname of the rep.
B. customer name for each customer associated with the rep.
C.total amount of money (excluding tax) billed for all customers associated with the rep.
Premiere Database schema:
-A Customer structure has attributes CustomerNumber, RepNum (assigned rep), etc.
-A Rep structure has attributes RepNum, etc.
-An Orders structure has attributes CustomerNumber, OrderNum, etc.
-An OrderLine structure has attributes OrderNum, NumOrdered, QuotedPrice(per -item), etc.
*Multiple order lines can have the same order number.
Sample desired output:
Q4 Output:
<results>
<rep repnum="20" firstname="Valerie" lastname="Kaiser">
<customer name="Al's Appliance and Sport"/>
<customer name="Kline's"/>
<customer name="All Season"/>
<Revenue total="$736.45"/>
</rep>
<rep repnum="35" firstname="Richard" lastname="Hull">
<customer name="Brookings Direct"/>
<customer name="The Everything Shop"/>
<customer name="Lee's Sport and Appliance"/>
<customer name="Deerfield's Four Seasons"/>
<Revenue total="$2,509.80"/>
</rep>
<rep repnum="65" firstname="Juan" lastname="Perez">
<customer name="Ferguson's"/>
<customer name="Bargains Galore"/>
<customer name="Johnson's Department Store"/>
<Revenue total="$5,664.89"/>
</rep>
</results>
The output I'm getting:
<results>
<rep>
<rep repnum="20" firstname="Valerie" lastname="Kaiser"/>
<customer name="Al's Appliance and Sport"/>
<customer name="Kline's"/>
<customer name="All Season"/>
<Revenue total="0"/>
</rep>
<rep>
<rep repnum="35" firstname="Richard" lastname="Hull"/>
<customer name="Brookings Direct"/>
<customer name="The Everything Shop"/>
<customer name="Lee's Sport and Appliance"/>
<customer name="Deerfield's Four Seasons"/>
<Revenue total="0"/>
</rep>
<rep>
<rep repnum="65" firstname="Juan" lastname="Perez"/>
<customer name="Ferguson's"/>
<customer name="Bargains Galore"/>
<customer name="Johnson's Department Store"/>
<Revenue total="0"/>
</rep>
</results>
Revenue per rep is 0!
My code:
<results>
{
for $r in doc("../premiere/Rep.xml")//Rep
let $c := doc("../premiere/Customer.xml")//Customer[RepNum = $r/RepNum]
return
<rep>
<rep repnum = "{$r/RepNum}" firstname="{$r/FirstName}" lastname="{$r/LastName}"/>
{for $customer in $c
return
<customer name= "{ $customer/CustomerName}"/>}
<Revenue total= "{sum(
for $customer in $c
let $o := doc("../premiere/Orders.xml")//Orders[CustomerNumber = $customer/CustomerNumber]
let $customerAmount := sum(
for $order in $o
let $l := doc("../premiere/OrderLine.xml")//OrderLine[OrderNum = $order/OrderNum]
let $orderAmount := format-number(sum(
for $lineItem in $l
let $LineAmount := (data($lineItem/NumOrdered) * data($lineItem/QuotedPrice))
return
$LineAmount
), '$,000.00')
return
$orderAmount
)
return
$customerAmount
)}"
/>
</rep>
}
</results>
Thanks in advance!
First, as #joewiz pointed out, you format the partial sums into strings before you sum them up, which should definitely raise an error if executed. Since it does not, I would suspect that the outer sum never adds up any values.
You did not provide any example data, but I found this SQL file for a Premiere data set that seems to match yours. One important difference is that CUSTOMER has an attribute CUSTOMER_NUM, not CUSTOMER_NUMBER like in your description. If the element is also called CustomerNum instead of CustomerNumber in your data set, then let $o := doc("../premiere/Orders.xml")//Orders[CustomerNumber = $customer/CustomerNumber] will silently be evaluated to the empty sequence and the sum is 0.
Here is a version of your query that works for me and produces the exact output you wanted when evaluated on the values from the SQL file:
<results>{
for $r in doc("../premiere/Rep.xml")//Rep
let $c := doc("../premiere/Customer.xml")//Customer[RepNum = $r/RepNum]
return <rep>{
<rep repnum = "{$r/RepNum}" firstname="{$r/FirstName}" lastname="{$r/LastName}"/>,
for $customer in $c
return <customer name="{$customer/CustomerName}"/>,
let $total :=
sum(
for $customer in $c
for $order in doc("../premiere/Orders.xml")//Orders[CustomerNum = $customer/CustomerNumber]
for $lineItem in doc("../premiere/OrderLine.xml")//OrderLine[OrderNum = $order/OrderNum]
return $lineItem/NumOrdered * $lineItem/QuotedPrice
)
return <Revenue total="{format-number($total, '$,000.00')}"/>
}</rep>
}</results>
I have simplified it by merging the two sums into one and inlining a few variables, and I moved the number formatting out to the correct spot.

ORA-19228: XPST0008 - undeclared identifier in oracle sql

I have xml string which i have mentioned below and i want to extract code from xml string. I have written below select query to extract the value from <ax2140:code>0</ax2140:code> which is 0 but i am getting error as
ORA-19228: XPST0008 - undeclared identifier: prefix 'ax2140' local-name 'ax2140:code'
19228. 00000 - "XPST0008 - undeclared identifier: prefix '%s' local-name '%s'"
Here is my xml string-
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns:placeShopOrderResponse xmlns:ns="http://service.soap.CDRator.com">
<ns:return xmlns:ax2127="http://data.soap.CDRator.com/xsd"
xmlns:ax2129="http://webshop.data.soap.CDRator.com/xsd"
xmlns:ax2130="http://core.data.soap.CDRator.com/xsd"
xmlns:ax2133="http://signup.data.soap.CDRator.com/xsd"
xmlns:ax2134="http://core.signup.data.soap.CDRator.com/xsd"
xmlns:ax2139="http://result.service.soap.CDRator.com/xsd"
xmlns:ax2140="http://core.result.service.soap.CDRator.com/xsd"
xmlns:ax2147="http://webshop.result.service.soap.CDRator.com/xsd"
xmlns:ax2148="http://mandate.result.service.soap.CDRator.com/xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ax2147:PlaceShopOrderResultDTO">
<ax2130:id xsi:nil="true" />
<ax2140:code>0</ax2140:code>
<ax2140:description>OK</ax2140:description>
<ax2127:statusId>1</ax2127:statusId>
<ax2127:subscriptionCondition xsi:type="ax2127:SubscriptionStatusDTO">
<ax2130:id xsi:nil="true" />
<ax2140:code>0</ax2140:code>
<ax2140:description>OK</ax2140:description>
<ax2130:imsi xsi:nil="true" />
<ax2130:phoneNumber>NO_NUMBER</ax2130:phoneNumber>
<ax2127:imei xsi:nil="true" />
<ax2127:simCard xsi:nil="true" />
</ax2127:teleService>
</ax2147:subscriptions>
</ns:return>
</ns:placeShopOrderResponse>
</soapenv:Body>
</soapenv:Envelope>
Here is my select query:
SELECT ID,xt_code.CODE
FROM TEMP_SOAP_MONITORING_MSP sm
CROSS JOIN XMLTable(XMLNAMESPACES (
'http://core.result.service.soap.CDRator.com/xsd' as "ax2140_code"
),
'for $i in //ax2140:code return $i'
passing XMLType(sm.RESPONSE_XML)
columns "CODE" VARCHAR2(50) path '/') xt_code;
Use "ax2140" instead of "ax2140_code" in the query.

How to delete duplicate XML data with specific attribute

I have this SQL query
SELECT r.Address.value('(//City)[1]','VARCHAR(MAX)') "City/#Title" ,
(SELECT r1.FirstName , r1.SecondName
FROM Reader r1
where r.Address.value('(//City)[1]','VARCHAR(MAX)') = r1.Address.value('(//City)[1]','VARCHAR(MAX)')
FOR XML RAW('Reader'),TYPE) AS "City/Readers"
FROM Reader r
FOR XML PATH(''),ROOT('Cities');
This query produces such data:
<Cities>
<City Title="New York">
<Readers>
<Reader FirstName="JON" SecondName="SHOW" />
<Reader FirstName="Poll" SecondName="Aeron" />
</Readers>
</City>
<City Title="New York">
<Readers>
<Reader FirstName="JON" SecondName="SHOW" />
<Reader FirstName="Poll" SecondName="Aeron" />
</Readers>
</City>
<City Title="Kharkiv">
<Readers>
<Reader FirstName="Slavik" SecondName="Romanov" />
</Readers>
</City>
<City Title="Boca Juniors">
<Readers>
<Reader FirstName="Julio " SecondName="Pedro" />
</Readers>
</City>
<City Title="London">
<Readers>
<Reader FirstName="Johnny " SecondName="Smith" />
</Readers>
</City>
</Cities>
I want to delete duplicate elements, exactly the second node.
The query must generate data without the duplicates
Selecting distinct City before constructing the XML would be more efficient. This is one possible way using Common Table Expression (CTE) :
;WITH Cities
AS(
SELECT DISTINCT r.Address.value('(//City)[1]','VARCHAR(MAX)') As City
FROM Reader r
)
SELECT
c.City "City/#Title",
(SELECT r1.FirstName , r1.SecondName
FROM Reader r1
where c.City = r1.Address.value('(//City)[1]','VARCHAR(MAX)')
FOR XML RAW('Reader'),TYPE) AS "City/Readers"
FROM Cities c
FOR XML PATH(''),ROOT('Cities');
Fiddle Demo

XQuery retrieval of a value

I have the following T-SQL that determines if a row exists using two criteria:
Declare #x xml = '
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="page" itemValue="Confirmation" itemType="string" />
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="visited" itemValue="false" itemType="bool" />'
SELECT #x.exist('/row[#Node eq "root.local.navigation[7]"] and /row[#itemValue eq "Confirmation"]')
Question: Given the above SELECT, how can I SELECT the second row's itemValue?
i.e. Since there's a row with Node="root.local.navigation[7]" and itemValue="Confirmation", return the itemType value in the row where node is the same and itemKey="visited"?
How about this:
declare #x xml = '
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="page" itemValue="Confirmation" itemType="string" />
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="visited" itemValue="false" itemType="bool" />'
select case when #x.exist('/row[#Node eq "root.local.navigation[7]"] and /row[#itemValue eq "Confirmation"]') = 1
then #x.value('/row[#Node eq "root.local.navigation[7]" and #itemKey eq "visited"][1]/#itemType', 'varchar(50)')
end as item_type