Is there a way to select all elements from xmlvalue.value method instead of specifying [1] - sql

I want to access all instances and not specify in XMLvalues.value method like 1
I have joined the nodes using outer apply but I need to specify instances number
SELECT
Id ,
XmlValues2.value('(date1)[1]', 'DateTime') AS date1 ,
XmlValues.value('(name)[1]', 'varchar(1299)') AS bank ,
XmlValues.value('(country)[1]', 'varchar(1299)') AS Country ,
FROM
Temp_board I OUTER APPLY I.board.nodes('/Report/basicInfo/report') AS xmlTableInner(XmlValues2)
OUTER APPLY XmlValues2.nodes('/Report/basicInfo/bank') AS xmlTable(XmlValues)
WHERE
Id ='235908235'
So I want the result of all nodes elements and not just the first instant
Attached screenshot of xml.I want to access all instances of transactions.XML example

For your next question please try to set up a MCVE. This is a stand along self-running example to reproduce your issue. And please do not provide code or data as pictures. This means, that somebody else has to type this in...
It is probleamtic, that the XML's picture does not inlcude the root tag and eventually existing namespaces. Furthermore, the targeted <Transaction> elements are completely empty in your picture... Furthermore, the code you provide in your question has nothing to do with the XML you show...
I think you can understand, that everything must be pure guessing and my magic crystall ball is out for cleaning at the moment ;-)
This time I do the mcve for you. A lot is just guessing to show the principles. Try this out and try to adopt it for your actual issues:
Declare a mockup-XML:
DECLARE #xml XML=
N'<SomeRootNode>
<accountList>
<Transaction SomeAttribute="blah1">
<SomeSubElement subAttr="sub1">element1</SomeSubElement>
</Transaction>
<Transaction SomeAttribute="blah2">
<SomeSubElement subAttr="sub2">element2</SomeSubElement>
</Transaction>
<Transaction SomeAttribute="blah3">
<SomeSubElement subAttr="sub3">element3</SomeSubElement>
</Transaction>
</accountList>
</SomeRootNode>';
--The query
SELECT tr.value('#SomeAttribute','varchar(100)') TheAttributeInTransactionElement
,tr.value('(SomeSubElement/#subAttr)[1]','varchar(100)') TheAttributeInTheSubElement
,tr.value('(SomeSubElement/text())[1]','varchar(100)') TheSubElementsContent
FROM #xml.nodes('/SomeRootNode/accountList/Transaction') A(tr);
The idea in short:
- .nodes() will dive into <SomeRootNode>, deeper into <accountList> and will return all <Transaction> elements found at this level
- The result of .nodes() is a derived table called A with one single XML-typed column tr, representing each Transaction in a single row.
- The .value() can pick attributes directly
- The .value() can use any kind of XPath/XQuery to fetch values from within the structure.
UPDATE Your XML has one more level Account
Try this
DECLARE #xml XML=
N'<SomeRootNode>
<accountList>
<Account>
<number>1</number>
<Transaction SomeAttribute="blah1">
<SomeSubElement subAttr="sub1">element1</SomeSubElement>
</Transaction>
<Transaction SomeAttribute="blah2">
<SomeSubElement subAttr="sub2">element2</SomeSubElement>
</Transaction>
<Transaction SomeAttribute="blah3">
<SomeSubElement subAttr="sub3">element3</SomeSubElement>
</Transaction>
</Account>
<Account>
<number>2</number>
<Transaction SomeAttribute="blah2-1">
<SomeSubElement subAttr="sub2-1">element2-1</SomeSubElement>
</Transaction>
<Transaction SomeAttribute="blah2-2">
<SomeSubElement subAttr="sub2-2">element2-2</SomeSubElement>
</Transaction>
<Transaction SomeAttribute="blah2-3">
<SomeSubElement subAttr="sub2-3">element2-3</SomeSubElement>
</Transaction>
</Account>
</accountList>
</SomeRootNode>';
SELECT acc.value('(number/text())[1]','int') AccountNumber
,tr.value('#SomeAttribute','varchar(100)') TheAttributeInTransactionElement
,tr.value('(SomeSubElement/#subAttr)[1]','varchar(100)') TheAttributeInTheSubElement
,tr.value('(SomeSubElement/text())[1]','varchar(100)') TheSubElementsContent
FROM #xml.nodes('/SomeRootNode/accountList/Account') A(acc)
OUTER APPLY A.acc.nodes('Transaction') B(tr);
Now we can use two calls to .nodes(). The first will return all <Account> elements within <accountList>, the second will return the nested <Transaction> elements as it is working with A.acc as input.

Related

SQL query for XML data

I have a SQL Server database table with a column called XML that contains XML data which is structured like this:
<Item xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://test/data">
<Roots>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
</Roots>
I want to use T-SQL to get the Value where Name = Total. I have tried the following but it isn't returning any data:
SELECT [XML]
FROM [BusinessAccount]
WHERE [XML].value('(/Root/Name)[13]', 'VARCHAR(MAX)') LIKE '%Total%'
If anyone could tell me where I've gone wrong?
You are missing the required WITH XMLNAMESPACES for your XML and the path is incorrect.
If you want to bring back rows where the 13th element consists of the text Total you can use the below.
WITH XMLNAMESPACES (DEFAULT 'http://test/data')
SELECT [XML]
FROM [BusinessAccount]
WHERE 1 = [XML].exist('(/Item/Roots/Root/Name)[13][text() = "Total"]')
Otherwise you can add the WITH XMLNAMESPACES to your original query and fix the path there too.
You need to specify namespaces. You can then match <Name> and <Value> pairs and extract the contents of <Value> like so:
SELECT NameNode.value('declare namespace x="http://test/data"; (../x:Value)[1]', 'varchar(100)')
FROM [BusinessAccount]
CROSS APPLY [XML].nodes('declare namespace x="http://test/data"; //x:Root/x:Name') AS n(NameNode)
WHERE NameNode.value('.', 'varchar(100)') = 'Total'
Demo on db<>fiddle

Loop and modify xml nodes SQL Server

I want to loop through xml nodes in SQL Server, and create a copy of each node containing ','.
For example, for the following xml :
declare #answerXML xml = '<answers><answer part="1">answer,test0</answer><answer part="1">answer,test1</answer></answers>'
I want to be modified to become the following :
declare #answerXML xml = '<answers><answer part="1">answer,test0</answer><answer part="1">answer, test0</answer><answer part="1">answer,test1</answer><answer part="1">answer, test1</answer></answers>'
(Each node is duplicated, and in the added node, a space is added after the comma).
I was planing in using something like this :
SELECT
T.ref.value('.', 'varchar(256)') AS Answer
FROM
(SELECT
[Xml] = #answerXML.query('for $i in data(/answers/answer)
return element temp { $i }')
) A
CROSS APPLY
A.Xml.nodes('/temp') T(ref)
But no use, it seems it's too complicated.
Can anyone help in how to loop and update XML in T-SQL?
Thank you in advance,
You can try to shred the XML and rebuild it from scratch:
declare #answerXML xml=
'<answers>
<answer part="1">answer,test0</answer>
<answer part="1">answer,test1</answer>
</answers>';
SELECT a.value(N'#part',N'int') AS [answer/#part]
,a.value(N'text()[1]',N'nvarchar(max)') AS [answer]
,''
,a.value(N'#part',N'int') AS [answer/#part]
,REPLACE(a.value(N'text()[1]',N'nvarchar(max)'),',',', ') AS [answer]
FROM #answerXML.nodes(N'/answers/answer') AS A(a)
FOR XML PATH(''),ROOT('answers')
The result
<answers>
<answer part="1">answer,test0</answer>
<answer part="1">answer, test0</answer>
<answer part="1">answer,test1</answer>
<answer part="1">answer, test1</answer>
</answers>

XML import into SQL Server

This is my first time working with XML files. I have been able to read the file into a table. Now I am trying to access the data elements to insert / update into ERP.
I am getting stuck using https://learn.microsoft.com/en-us/sql/t-sql/functions/openxml-transact-sql as a guide.
I am only attempting a single retrieval now, trying to keep things simple:
XML:
<TranscriptRequest xmlns="urn:org:pesc:message:TranscriptRequest:v1.0.0">
<TransmissionData>
<DocumentID xmlns="">88895-20170227180832302-ccd</DocumentID>
<CreatedDateTime xmlns="">2017-02-27T18:08:32.303-08:00</CreatedDateTime>
<DocumentTypeCode xmlns="">Request</DocumentTypeCode>
<TransmissionType xmlns="">Original</TransmissionType>
<Source xmlns="">
<Organization>
<DUNS>626927060</DUNS>
<OrganizationName>AVOW</OrganizationName>
</Organization>
</Source>
<Destination xmlns="">
<Organization>
<OPEID>3419</OPEID>
<OrganizationName>Charleston Southern University</OrganizationName>
</Organization>
</Destination>
<DocumentProcessCode xmlns="">PRODUCTION</DocumentProcessCode>
</TransmissionData>
<Request>
<CreatedDateTime xmlns="">2017-02-27T00:00:00.000-08:00</CreatedDateTime>
<Requestor xmlns="">
<Person>
<Birth>
<BirthDate>1985-01-01</BirthDate>
</Birth>
<Name>
<FirstName>Chad</FirstName>
<LastName>test2</LastName>
</Name>
<AlternateName>
<FirstName>Chad</FirstName>
<LastName>Walker</LastName>
<CompositeName>Walker, Chad</CompositeName>
</AlternateName>
<Contacts>
<Address>
<AddressLine>10260 west st</AddressLine>
<City>Denver</City>
<StateProvinceCode>CO</StateProvinceCode>
<PostalCode>80236</PostalCode>
</Address>
<Phone>
<CountryPrefixCode>1</CountryPrefixCode>
<AreaCityCode>303</AreaCityCode>
<PhoneNumber>8152848</PhoneNumber>
</Phone>
<Email>
<EmailAddress>cwalker#parchment.com</EmailAddress>
</Email>
</Contacts>
</Person>
</Requestor>
My SQL:
USE TMSEPRD
DECLARE #XML AS XML, #hDoc AS INT, #SQL NVARCHAR (MAX)
SELECT #XML = [XMLData] FROM [dbo].[CSU_Parchment_XMLwithOpenXML]
EXEC sp_xml_preparedocument #hDoc OUTPUT, #XML
SELECT BirthDate
FROM OPENXML(#hDoc, 'ROOT/Request/Requestor/Person/Birth/Birthdate')
WITH
(
BirthDate varchar(20) '../#BirthDate'
)
EXEC sp_xml_removedocument #hDoc
GO
Birthdate remains blank. So far I have tried eliminating the ../ in front of #Birthdate, I have tried removing the #, I have tried removing Birthdate from the path.
Using Xml Data Type Methods
DECLARE #XML XML='<TranscriptRequest xmlns="urn:org:pesc:message:TranscriptRequest:v1.0.0">
<TransmissionData>
<DocumentID xmlns="">88895-20170227180832302-ccd</DocumentID>
<CreatedDateTime xmlns="">2017-02-27T18:08:32.303-08:00</CreatedDateTime>
<DocumentTypeCode xmlns="">Request</DocumentTypeCode>
<TransmissionType xmlns="">Original</TransmissionType>
<Source xmlns="">
<Organization>
<DUNS>626927060</DUNS>
<OrganizationName>AVOW</OrganizationName>
</Organization>
</Source>
<Destination xmlns="">
<Organization>
<OPEID>3419</OPEID>
<OrganizationName>Charleston Southern University</OrganizationName>
</Organization>
</Destination>
<DocumentProcessCode xmlns="">PRODUCTION</DocumentProcessCode>
</TransmissionData>
<Request>
<CreatedDateTime xmlns="">2017-02-27T00:00:00.000-08:00</CreatedDateTime>
<Requestor xmlns="">
<Person>
<Birth>
<BirthDate>1985-01-01</BirthDate>
</Birth>
<Name>
<FirstName>Chad</FirstName>
<LastName>test2</LastName>
</Name>
<AlternateName>
<FirstName>Chad</FirstName>
<LastName>Walker</LastName>
<CompositeName>Walker, Chad</CompositeName>
</AlternateName>
<Contacts>
<Address>
<AddressLine>10260 west st</AddressLine>
<City>Denver</City>
<StateProvinceCode>CO</StateProvinceCode>
<PostalCode>80236</PostalCode>
</Address>
<Phone>
<CountryPrefixCode>1</CountryPrefixCode>
<AreaCityCode>303</AreaCityCode>
<PhoneNumber>8152848</PhoneNumber>
</Phone>
<Email>
<EmailAddress>cwalker#parchment.com</EmailAddress>
</Email>
</Contacts>
</Person>
</Requestor>
</Request>
</TranscriptRequest>';
SQL query:
with xmlnamespaces('urn:org:pesc:message:TranscriptRequest:v1.0.0' as ns)
select t.n.value('BirthDate[1]','date')
from #XML.nodes('ns:TranscriptRequest/ns:Request/Requestor/Person/Birth') t(n);
Namespaces
How is this XML generated? Is this under your control? What makes me wonder are various xmlns="" This is defining empty default namespaces over and over... Allthough there is one default NS xmlns="urn:org:pesc:message:TranscriptRequest:v1.0.0" in the first line, which seems to be correct.
For this namespace issue there are many solutions:
Create this without the empty xmlns - if this is under your control
Use REPLACE and cut away xmlns="" (be aware of the leading blank!) on string level, before you write this into your xml typed column / variable
use a wildcard (*:) for the namespaces of the first two levels
Define a namespace alias for the default and let the empty namespaces be the default for the lower levels
Structure
Your XML seems to be plain 1:1, only 1 source, 1 destination, one reques. And only 1 person within <Request> and again only 1 Name and so on below <Person>. There is a <Contacts> element, which sounds like 1:n, but it seems to include only 1 address, 1 phone... Reading plain 1:1 is easy...
I will use a CTE with two calls in .query() for an easy approach to solve the namespace issue with a wildcard. This allows to read the rest without namespaces:
some examples how to read your elements, the rest is up to you...
WITH levels AS
(
SELECT #xml.query(N'/*:TranscriptRequest/*:TransmissionData/*') AS td
,#xml.query(N'/*:TranscriptRequest/*:Request/*') AS rq
)
SELECT --TransmissionData
td.value(N'(DocumentID/text())[1]',N'nvarchar(max)') AS DocumentID
,td.value(N'(CreatedDateTime/text())[1]',N'datetime') AS td_CreatedDateTime
--more elemenst...
,td.value(N'(Source/Organization/DUNS/text())[1]',N'bigint') AS Source_Organization_DUNS
--more elements...
,td.value(N'(Destination/Organization/OPEID/text())[1]',N'bigint') AS Destination_Organization_OPEID
--Request
,rq.value(N'(CreatedDateTime/text())[1]',N'datetime') AS rq_CreatedDateTime
--Request-Person
,rq.value(N'(Requestor/Person/Name/FirstName/text())[1]',N'nvarchar(max)') AS Requestor_FirstName
--Contacts
,rq.value(N'(Requestor/Person/Contacts/Address/AddressLine/text())[1]',N'nvarchar(max)') AS Requestor_AddressLine
FROM levels

Remove Varying levels of empty XML tags in SQL Server

I am using FOR XML EXPLICIT to union to convert some SQL table info into an XML file. It's all working, but I have loads of empty tags in the results, at all sorts of different levels. I would like to remove all the empty tags, but keep the top level for each group. Here's an example of the sort of thing I mean:
Using this example bit of nonsense XML, I can remove the bottom level empty nodes with a .modify xquery:
DECLARE #XML XML =
'<Whatever>
<GlassesTypes>
<GlassesType />
</GlassesTypes>
<ExpressionOfJoy>
<FellOver>Y</FellOver>
</ExpressionOfJoy>
<Flights>
<Flight>
<Bookings>
<Booking>
<Segments>
<Segment />
</Segments>
</Booking>
</Bookings>
</Flight>
</Flights>
</Whatever>'
SELECT #XML as Before
SET #Xml.modify('delete //*[not(node())]');
SELECT #XML AS After
This has done exactly what I want with 'GlassesTypes', but there are still levels of empty nodes in 'Flights'. I could repeat the same modify command over and over for each level to get up to the point where only 'Flights' is displayed, but doing so will delete the 'GlassesTypes' empty placeholder:
DECLARE #XML XML =
'<Whatever>
<GlassesTypes>
<GlassesType />
</GlassesTypes>
<ExpressionOfJoy>
<FellOver>Y</FellOver>
</ExpressionOfJoy>
<Flights>
<Flight>
<Bookings>
<Booking>
<Segments>
<Segment />
</Segments>
</Booking>
</Bookings>
</Flight>
</Flights>
</Whatever>'
SELECT #XML as Before
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SELECT #XML AS After
Is there any way in which I can delete all empty nodes, until the penultimate empty node, regardless of how many levels a group contains? The ideal result would be this:
<Whatever>
<GlassesTypes />
<ExpressionOfJoy>
<FellOver>Y</FellOver>
</ExpressionOfJoy>
<Flights />
</Whatever>
In this example, there is only 'Whatever' tag, but in the real data, there might be several repeated, all with different levels of information, encompassed by a root tag or equivalent.
Any help much appreciated!
Thanks,
Mark
You can define your version of "empty" as not containing any descendant attribute or text nodes, instead of as not containing any descendant nodes. Then retain children of <Whatever> by only selecting descendants of its children:
/Whatever/*//*[empty(.//text() | .//attribute())]

Extracting XML data in SQL - too many cross apply statements

I have an xml document containing details from a Statement:
<Statement>
<Id />
<Invoices>
<Invoice>
<Id />
<Date />
<AmountDue />
etc.
</Invoice>
<Invoice>
<Id />
<Date />
<AmountDue />
etc.
</Invoice>
<Invoice>
<Id />
<Date />
<AmountDue />
etc.
</Invoice>
</Invoices>
</Statement>
This works fine for the Statement specific details:
SET #statementId = #xml.value('(Id)[1]', 'UNIQUEIDENTIFIER');
but it requires a singleton, and only returns the first value. I need ALL of the values for the invoices, not just the first so a singleton won't work.
I am able to get the information out using cross apply statements like this:
SELECT
#statementId AS STATEMENT_ID
Id.value('.', 'uniqueidentifier') AS INVOICE_ID
Date.value('.', 'smalldatetime') AS INVOICE_DATE
Due.value('.', 'decimal') AS INVOICE_AMOUNT_DUE
FROM #xml.nodes('Statement') A(S)
cross apply S.nodes('Invoices/Invoice') B(InvoiceD)
cross apply InvoiceD.nodes('Id') C(Id)
cross apply InvoiceD.nodes('Date') D(Date)
cross apply InvoiceD.nodes('AmountDue') E(Due)
This returns an Id, date, and amount from each Invoice in the Statement - perfect.
My problem comes when I try to extract all of the invoice details. I currently have seven cross apply statements and I got the following message:
"The query processor ran out of internal resources and could not
produce a query plan. This is a rare event and only expected for
extremely complex queries or queries that reference a very large
number of tables or partitions. Please simplify the query. If you
believe you have received this message in error, contact Customer
Support Services for more information."
What I want to do is have one cross apply for the Invoice and narrow down the exact field in the select statement, but unless I use '.' I must make the statement return a singleton and I don't get all of the data that I need.
I have done some research about specifying a namespace within the select statement, but all of the examples set the namespace to be an http address instead of a node in an xml document and I haven't gotten anything to return yet using this approach.
The result I'm looking for is something like this, but with more Invoice Details:
STATEMENT_ID INVOICE_ID INVOICE_DATE INVOICE_AMOUNT_DUE ...
Statement-1-Id Invoice-1-Id Invoice-1-Date Invoice-1-AmountDue ...
Statement-1-Id Invoice-2-Id Invoice-2-Date Invoice-2-AmountDue ...
Statement-1-Id Invoice-3-Id Invoice-3-Date Invoice-3-AmountDue ...
Where should I go from here?
EDIT: I removed some unnecessary information. Getting all of the invoice-specific details is my goal here.
select #XML.value('(Statement/Id/text())[1]', 'uniqueidentifier') as StatementId,
T.N.value('(Id/text())[1]', 'uniqueidentifier') as InvoiceId,
T.N.value('(Date/text())[1]', 'smalldatetime') as InvoiceDate,
T.N.value('(AmountDue/text())[1]', 'decimal') as AmountDue
from #XML.nodes('/Statement/Invoices/Invoice') as T(N)
.nodes will shred your XML to rows so that each row T.N is pointing to an Invoice node of its own. On that node there is only a single Id node so fetching the value specifying a singleton Id[1] works.
You can use Id[1] or (Id/text())[1] but the latter will give you a more efficient execution plan.