XML formatting in SQL Server xml path - sql

I am trying to get a particular XML format by querying SQL Server.
My nested query looks like this:
SELECT
#xml = (SELECT
AdmissionID AS "#encounter_number",
'NDIS' AS organisation_code,
'NDIS' AS encounter_type,
(SELECT 'true' as "#delete",
(SELECT 'NDIS' AS class_type, AdmissionDate AS start_date_time
FOR XML PATH ('financial_class'), TYPE)
FOR XML PATH('financial_classes'), TYPE),
(SELECT URNumber AS 'payor/#entity_number', 'Patient' AS slot, CONVERT (varchar(10), AdmissionDate, 126) AS start_date
FOR XML PATH('payor'), ROOT ('payors'), ELEMENTS, TYPE),
URNumber AS "recipient/#entity_number",
'H' AS referral_source,
'H' AS separation_status,
AdmissionDate AS start_date_time
FROM
HMS
FOR XML PATH('encounter'), ROOT ('pbrc'), TYPE)
which returns this output:
<pbrc>
<encounter encounter_number="525241">
<organisation_code>NDIS</organisation_code>
<encounter_type>NDIS</encounter_type>
<financial_classes delete="true">
<financial_class>
<class_type>NDIS</class_type>
<start_date_time>2018-06-26T00:00:00</start_date_time>
</financial_class>
</financial_classes>
<payors>
<payor>
<payor entity_number="4208151" />
<slot>Patient</slot>
<start_date>2018-06-26</start_date>
</payor>
</payors>
<recipient entity_number="4208151" />
<referral_source>H</referral_source>
<separation_status>H</separation_status>
<start_date_time>2018-06-26T00:00:00</start_date_time>
</encounter>
</pbrc>
But I am trying to achieve the following format for payor with proper payor closing tag and not the short form,
-<payors>
-<payor>
<payor entity_number="8195991"> </payor>
<slot>Patient</slot>
<start_date>2019-06-01</start_date>
</payor>
</payors>
Any suggestion please?

This was a comment, but I shifted it into the answer:
Are you aware of the fact, that - semantically - there's no difference
between <SomeElement /> and <SomeElement></SomeElement>? Is the
blank between <payor> and </payor> important? In fact, it is up
to the presenting engine, how this will be looking. The same XML might
appear in different shape depeding on the viewer...
You can try something along this:
DECLARE #mockPayor TABLE(enitityNumber INT,slot VARCHAR(100));
INSERT INTO #mockPayor VALUES(123,'slot123')
--This will return the self-closed payor element
SELECT p.enitityNumber AS [payor/#entityNumber]
,p.slot
FROM #mockPayor p
FOR XML PATH('payor')
The result
<payor>
<payor entityNumber="123" />
<slot>slot123</slot>
</payor>
And this will return the proper payor closing tag:
SELECT p.enitityNumber AS [payor/#entityNumber]
,' ' AS [payor]
,p.slot
FROM #mockPayor p
FOR XML PATH('payor');
The result
<payor>
<payor entityNumber="123"> </payor>
<slot>slot123</slot>
</payor>
Some background:
The engine works this down and thinks:
Okay, there is an entityNumber attribute living within a payor element. Well I have to open the payor element and insert the entitiyNumber.
Now there is a slot element. So I have to close the payor without any content and open the slot element.
In the second statement the engine thinks:
Same as above
Now there is some content to be placed into the payor element
Same as above, but replace the bold part with after the content

Related

Unable to pull the results by passing input as xml path in oracle

I am just trying to pull the records bypassing the XML path in where the condition of SQL unfortunately I am unable to do.
Note: Pull the records bypassing input as salesteamid value. Please someone can help me to run the query without any failures.
Query :
SELECT * from activity where= ExtractValue('/ActivityId/Agent/Territory/SalesTeamId[contains(10669)]');
XML:
<Activity
xmlns="urn:astrazeneca:na:Activity:domain:3" RestrictionGroup="NONE"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="DiscussionActivityType" TransactionCode="I" CreatedOnDate="2019-11-21T16:04:29" UpdatedOnDate="2019-11-21T16:04:32" SourceCreatedByID="20303090" SourceCreatedByFirstName="Brandy" SourceCreatedByLastName="Nirider" SourceCreatedByRole="" SourceUpdatedByID="20303090" SourceUpdatedByFirstName="Brandy" SourceUpdatedByLastName="Nirider" SourceSystemCode="FSA" SystemOfRecordCode="FSA" RecordCompanyCode="AZN" SourceCountry="USA" SourceRegion="USA" SourceSystemGroup="VNA" SystemOfRecordGroup="VNA">
<ActivityId>
<ns2:ID
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2" SystemCode="VNA">a080P00001YZM7WQAX
</ns2:ID>
</ActivityId>
<Agent>
<EmployeeId>
<ns2:ID
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2" SystemCode="EMPLID">20303090
</ns2:ID>
</EmployeeId>
<Territory>
<ns2:TerritoryId
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2">
<ns2:ID SystemCode="AZ">20070009</ns2:ID>
</ns2:TerritoryId>
<ns2:TerritoryCode
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2">20070009
</ns2:TerritoryCode>
<ns2:Role
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2">PSS
</ns2:Role>
<ns2:Description
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2">Indianapolis C IN 2
</ns2:Description>
<ns2:SalesTeamId
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2">
<ns2:ID SystemCode="AZ">10669</ns2:ID>
</ns2:SalesTeamId>
<ns2:SalesTeamCode
xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2">D_ALPHA
</ns2:SalesTeamCode>
</Territory>
</Agent>
<Customer>
<AZCustomerMasterID SystemCode="AZ">58586509</AZCustomerMasterID>
<SourceCustomerID SystemCode="NAVVA">001U000000pMTySIAW</SourceCustomerID>
<CustomerType>HCP</CustomerType>
<CustomerActivityRole>participant</CustomerActivityRole>
</Customer>
<ReferenceContent>
<ReferenceName>Reported Adverse Reaction</ReferenceName>
<ReferenceType>action</ReferenceType>
<ReferenceId SystemCode="VNA">Reported Adverse Reaction</ReferenceId>
</ReferenceContent>
<StartDate>2019-11-21T16:03:00</StartDate>
<EndDate>2019-11-21T16:03:00</EndDate>
<Topic>
<Name>BYDUREON BCISE</Name>
<Product>
<AZBrandId SystemCode="AZ">881574</AZBrandId>
<AZProductId SystemCode="AZ">881574</AZProductId>
<SourceProductId SystemCode=""/>
<ReferenceProductId SystemCode=""/>
<ProductName>BYDUREON BCISE</ProductName>
</Product>
</Topic>
<ActivityAction>discussion</ActivityAction>
<ActivityStatus>completed</ActivityStatus>
<ActivityInitiatedBy>AstraZeneca agent</ActivityInitiatedBy>
<ActivityOutcome>success with identified customer</ActivityOutcome>
<CommunicationMode>in person</CommunicationMode>
<LocationSetting>Selling</LocationSetting>
<SourceValues>
<Status>Submitted</Status>
<SubStatus>Completed</SubStatus>
<Applet>Actions</Applet>
<Activity>Detail Only</Activity>
<InteractionCategory>A_Selling (PSS)</InteractionCategory>
<Type>Selling</Type>
</SourceValues>
<Priority>2</Priority>
<DiscussionTool>N</DiscussionTool>
<ActivityTypeCheck>DiscussionActivityType</ActivityTypeCheck>
</Activity>
Assuming you're trying to filter on an ID within the XML, you have an issue with syntax, you've supplied the root node as ActivityId instead of Activity, you aren't going down to the ID node, you're misusing contains - and that probably isn't what you want anyway; and you're ignoring the namespaces.
This will only find rows where the XML has a specific ID:
SELECT * from activity
where ExtractValue(xml,
'/Activity/Agent/Territory/ns2:SalesTeamId/ns2:ID/text()',
'xmlns="urn:astrazeneca:na:Activity:domain:3" xmlns:ns2="urn:astrazeneca:na:CommonTypes:domain:2"'
) = 10669;
But ExtractValue has long been deprecated, so you should use XMLQuery instead. Or in this context it would probably make more sense to use XMLExists, with the value you want embeded:
SELECT * from activity
where XMLExists('
declare default element namespace "urn:astrazeneca:na:Activity:domain:3";
declare namespace ns2="urn:astrazeneca:na:CommonTypes:domain:2";
/Activity/Agent/Territory/ns2:SalesTeamId/ns2:ID[text()=10669]
'
passing xml
);
or perhaps more usefully supplied as an argument:
SELECT * from activity
where XMLExists('
declare default element namespace "urn:astrazeneca:na:Activity:domain:3";
declare namespace ns2="urn:astrazeneca:na:CommonTypes:domain:2";
/Activity/Agent/Territory/ns2:SalesTeamId/ns2:ID[text()=$id]
'
passing xml, 10669 as "id"
);
db<>fiddle
If you then want to extract specific data from the XML in matching rows then look at XMLQuery or XMLTable - but that's a separate issue.

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

Reading dynamic XML nodes in SQL Server

I have the following XML structure:
set #MailXML =
'<MailingCompany>
<Mailman>
<Name>Jamie</Name>
<Age> 24 </Age>
<Letter>
<DestinationAddress> 440 Mountain View Parade </DestinationAddress>
<DestinationCountry> USA </DestinationCountry>
<OriginCountry> Australia </OriginCountry>
<OriginAddress> 120 St Kilda Road </OriginAddress>
</Letter>
</Mailman>
</MailingCompany>'
My SQL currently looks like this:
-- Mail Insertion
INSERT INTO mailDB.dbo.Mailman
SELECT
m.value('Name[1]','varchar(50)') as Name,
m.value('Age[1]','varchar(50)') as Age
FROM
#MailXML.nodes('/MailingCompany/Mailman') as A(m)
SET #MailPersonFK = SCOPE_IDENTITY();
-- Letter Insertion
INSERT INTO mailDB.dbo.Letter
SELECT
l.value('DestinationAddress[1]', 'varchar(50)') as DestinationAddress,
l.value('DestinationCountry[1]', 'varchar(50)') as DestinationCountry,
l.value('OriginCountry[1]', 'varchar(50)') as OriginCountry,
l.value('OriginAddress[1]', 'varchar(50)') as OriginAddress
#MailPersonFK as MailID
FROM
#MailXML.nodes('MailingCompany/Mailman/Letter') as B(l)
I am trying to extract the Mailman and Letter data into their own respective tables. I have got that working however my issue is that the MailCompany node is dynamic. Sometimes it may be MailVehicle, for example, and I still need
to read the corresponding Mailman and Letter node data and insert them into their own respective tables.
So both
FROM #MailXML.nodes('/MailingCompany/Mailman') as A(t)
and
FROM #MailXML.nodes('MailingCompany/Mailman/Letter') as B(l)
Will need to be changed to allow MailingCompany to be dynamic.
I have tried to extract the parent node and concatenate it into a string to put into the .nodes function like the following:
set #DynXML = '/' + #parentNodeVar + '/Mailman'
FROM #MailXML.nodes(#DynXML) as A(t)
However I get the following error:
The argument 1 of the XML data type method "nodes" must be a string literal.
How can I overcome this dynamic XML issue?
Thank you very much in advance
Look at this reduced example:
DECLARE #xml1 XML=
N'<MailingCompany>
<Mailman>
<Name>Jamie</Name>
<Letter>
<DestinationAddress> 440 Mountain View Parade </DestinationAddress>
</Letter>
</Mailman>
</MailingCompany>';
DECLARE #xml2 XML=
N'<OtherName>
<Mailman>
<Name>Jodie</Name>
<Letter>
<DestinationAddress> This is the other address </DestinationAddress>
</Letter>
</Mailman>
</OtherName>';
SELECT #xml1.value(N'(*/Mailman/Name)[1]','nvarchar(max)') AS Mailman_Name
,#xml1.value(N'(*/Mailman/Letter/DestinationAddress)[1]','nvarchar(max)') AS DestinationAddress
SELECT #xml2.value(N'(*/Mailman/Name)[1]','nvarchar(max)') AS Mailman_Name
,#xml2.value(N'(*/Mailman/Letter/DestinationAddress)[1]','nvarchar(max)') AS DestinationAddress
You can replace a node's name with *.
Another trick is the deep search with // (same result as before):
SELECT #xml1.value(N'(//Name)[1]','nvarchar(max)') AS Mailman_Name
,#xml1.value(N'(//DestinationAddress)[1]','nvarchar(max)') AS DestinationAddress
SELECT #xml2.value(N'(//Name)[1]','nvarchar(max)') AS Mailman_Name
,#xml2.value(N'(//DestinationAddress)[1]','nvarchar(max)') AS DestinationAddress
The general rule: Be as specific as possible.

Using Fields[0].Value to get XML from FOR XML RAW, ELEMENTS query is messed up

I have a query that uses FOR XML RAW, ELEMENTS to return a SELECT query as a structured XML document. However, when I get the result using a TSQLDataSet by using Fields[0].Value, the result is different from what I see when I run the query in SQL Server Management Studio.
What I see in the result from the TSQLDataSet:
੄customerIdфname၄governmentNumberไdebtorAddress1ไdebtorAddress2ไdebtorAddress3ไdebtorAddress4ࡄpostCodeୄcontactNameՄphonë́faxൄcustomerSinceՄtermsلactiveไcurrentBalanceلDebtorခŁ䄁ഃӤ
What I see in the result in SSMS:
<Debtor>
<customerId>C0E449E5B2C </customerId>
<name>New Customer 2 </name>
<governmentNumber> </governmentNumber>
<debtorAddress1>Address Line 1 </debtorAddress1>
<debtorAddress4>Address Line 4 </debtorAddress4>
<postCode>1234 </postCode>
<phone>1234567890 </phone>
<fax>1234567890 </fax>
<customerSince>2013-06-10T18:16:06.213</customerSince>
<terms>M </terms>
<active>true</active>
<currentBalance>0.0000</currentBalance>
</Debtor>
Is there a particular way it should be executed to get the right result?
AFAIK this is a DbExpress limitation. I know how overcome this, but using ADO (the returned data must be requested using a special parametrized object and a set of ADO streams). However you can use a workaround converting the XML data to a string in the server side sorrounding the sentence with a select (subquery) or just using a simple CAST statement.
For example if you sentence is like so
SELECT Foo, Bar FROM FooTable FOR XML RAW, ELEMENTS
you can rewrite to
SELECT (SELECT Foo, Bar FROM FooTable FOR XML RAW, ELEMENTS)
or you can rewrite to (use a CAST VARCHAR or NVARCHAR)
SELECT CAST( (SELECT Foo, Bar FROM FooTable FOR XML RAW, ELEMENTS) AS VARCHAR(MAX))
and finally
Retrieve the result like this
SQLDataSet1.Fields[0].AsString

Better way in TSQL to search xml for a node that doesn't exist

We have a source XML file that has an address node, and each node is supposed to have a zip_code node beneath in order to validate. We received a file that failed the schema validation because at least one node was missing it's zip_code (there were several thousand addresses in the file).
We need to find the elements that do not have a zip code, so we can repair the file and send an audit report to the source.
--declare #x xml = bulkcolumn from openrowset(bulk 'x:\file.xml',single_blob) as s
declare #x xml = N'<addresses>
<address><external_address_id>1</external_address_id><zip_code>53207</zip_code></address>
<address><external_address_id>2</external_address_id></address>
</addresses>'
declare #t xml = (
select #x.query('for $a in .//address
return
if ($a/zip_code)
then <external_address_id />
else $a/external_address_id')
)
select x.AddressID.value('.', 'int') AddressID
from #t.nodes('./external_address_id') x(AddressID)
where x.AddressID.value('.', 'int') > 0
GO
Really, it's the where clause that bugs me. I feel like I'm depending on a cast for a null value to 0, and it works, but I'm not really sure that it should. I tried a few variations with the .exist function, but I couldn't get the correct result.
If you just want to ensure that you are selecting address elements that have a zip_code element, then adjust your XPATH to include that criteria in a predicate filter:
/addresses/address[zip_code]
If you also want to ensure that the zip_code element also has a value, use a predicate filter for the zip_node to select those that have text() nodes:
/addresses/address[zip_code[text()]]
EDIT:
Actually, I'm looking for the
opposite. I need to identify the nodes
that don't have a zip, so we can
manually correct the source data.
So, if you want to identify all of the address elements that do not have a zip_code, you can specify it in the XPATH like this:
/addresses/address[not(zip_code)]
If you just want to locate those nodes that are missing their <zip_code> element, you could use something like this:
SELECT
ADRS.ADR.value('(external_address_id)[1]', 'int') as 'ExtAdrID'
FROM
#x.nodes('/addresses/address') as ADRS(ADR)
WHERE
ADRS.ADR.exist('zip_code') = 0
It uses the built-in .exist() method in XQuery to check the existence of a subnode inside an XML node.