TSQL - XML query help - sql-server

I have an XML in this format
<tests>
<test>
<testid>1</testid>
<testval>8</testval>
<testname>
<testid>1</testid>
<testname>test 1</testname>
</testname>
</test>
<test>
<testid>2</testid>
<testval>5</testval>
<testname>
<testid>2</testid>
<testname>test 2</testname>
</testname>
</test>
</tests>
using TSQL/XML query how do I achieve this result
[Testid][TestVal][TestName]
1 8 Test 1
2 5 Test 2

Try this:
declare #input XML = '<tests>
<test>
<testid>1</testid>
<testval>8</testval>
<testname>
<testid>1</testid>
<testname>test 1</testname>
</testname>
</test>
<test>
<testid>2</testid>
<testval>5</testval>
<testname>
<testid>2</testid>
<testname>test 2</testname>
</testname>
</test>
</tests>'
select
Tests.value('(testid)[1]', 'int') as 'TestID',
Tests.value('(testval)[1]', 'int') as 'TestVal',
Tests.value('(testname/testname)[1]', 'varchar(20)') as 'TestName'
FROM
#input.nodes('/tests/test') as List(Tests)
This gives you the desired output.
If you have a table of those XML columns, you might need to use a slightly different approach (using CROSS APPLY):
select
tbl.SomeValue, tbl.SomeOtherValue,
Tests.value('(testid)[1]', 'int') as 'TestID',
Tests.value('(testval)[1]', 'int') as 'TestVal',
Tests.value('(testname/testname)[1]', 'varchar(20)') as 'TestName'
FROM
dbo.YourTable tbl
CROSS APPLY
tbl.XmlColumn.nodes('/tests/test') as List(Tests)

Related

How do I query repeating XML child nodes inside parent repeating nodes?

I have the following XML stored in an nText column (not my design, older database). I need to pull the PolicyNumber and the CvgCode that is a property of Coverage child node.
<efs:Request
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:efs="http://www.slsot.org/efs"
xsi:schemaLocation="http://www.slsot.org/efs
http://efs.slsot.org/efs/xsd/SlsotEfsSchema2.xsd">
<EfsVersion>2.0</EfsVersion>
<Batch BatchType="N" AgLicNo="12345" ItemCnt="69">
<EFSPolicy>
<PolicyNumber>POL12345</PolicyNumber>
<Binder>0086592YZ</Binder>
<TransType>N</TransType>
<Insured>Dummy Co LLC</Insured>
<ZipCode>75225</ZipCode>
<ClassCd>99930</ClassCd>
<PolicyFee>35.00</PolicyFee>
<TotalTax>36.62</TotalTax>
<TotalStampFee>1.13</TotalStampFee>
<TotalGross>792.75</TotalGross>
<EffectiveDate>09/17/2018</EffectiveDate>
<ExpirationDate>09/17/2019</ExpirationDate>
<IssueDate>09/20/2018</IssueDate>
<ContUntilCancl>N</ContUntilCancl>
<FedCrUnion>N</FedCrUnion>
<AORFlag>N</AORFlag>
<CustomID>043684</CustomID>
<WindStormExclusion>N</WindStormExclusion>
<CorrectionReEntry>N</CorrectionReEntry>
<Coverages>
<Coverage CvgCode="9325">720.00</Coverage>
</Coverages>
<Securities>
<Company CoNumber="80101168">100.00</Company>
</Securities>
</EFSPolicy>
<EFSPolicy>
...
</EFSPolicy>
</Batch>
</efs:Request>
And here is the SQL code I am using to extract the PolicyNumber (so far).
with cte_table(BatchID, xmlData)
AS
(SELECT BatchID, CAST(CAST(xmlData AS VARCHAR(MAX)) AS XML) from
Batches)
select
s.BatchID
,t.c.value('PolicyNumber[1]', 'varchar(max)') as PolicyNumber
from cte_table as s
cross apply s.xmlData.nodes('/*:Request/Batch/EFSPolicy') as t(c)
where BatchID in (select batchID from Batches where CreateDate between '1/1/19' and getdate())
I have tried a second CROSS APPLY on the Coverages node, but that was giving me all the Coverage values (not CvgCode property) for every batch, so my result set was 100+ times too many rows. I assume that was due to the 2nd CROSS APPLY, is there a INNER JOIN type CROSS APPLY?
You need to declare your namespaces to retrieve the data:
DECLARE #XML xml = '<efs:Request
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:efs="http://www.slsot.org/efs"
xsi:schemaLocation="http://www.slsot.org/efs
http://efs.slsot.org/efs/xsd/SlsotEfsSchema2.xsd">
<EfsVersion>2.0</EfsVersion>
<Batch BatchType="N" AgLicNo="12345" ItemCnt="69">
<EFSPolicy>
<PolicyNumber>POL12345</PolicyNumber>
<Binder>0086592YZ</Binder>
<TransType>N</TransType>
<Insured>Dummy Co LLC</Insured>
<ZipCode>75225</ZipCode>
<ClassCd>99930</ClassCd>
<PolicyFee>35.00</PolicyFee>
<TotalTax>36.62</TotalTax>
<TotalStampFee>1.13</TotalStampFee>
<TotalGross>792.75</TotalGross>
<EffectiveDate>09/17/2018</EffectiveDate>
<ExpirationDate>09/17/2019</ExpirationDate>
<IssueDate>09/20/2018</IssueDate>
<ContUntilCancl>N</ContUntilCancl>
<FedCrUnion>N</FedCrUnion>
<AORFlag>N</AORFlag>
<CustomID>043684</CustomID>
<WindStormExclusion>N</WindStormExclusion>
<CorrectionReEntry>N</CorrectionReEntry>
<Coverages>
<Coverage CvgCode="9325">720.00</Coverage>
</Coverages>
<Securities>
<Company CoNumber="80101168">100.00</Company>
</Securities>
</EFSPolicy>
<EFSPolicy>
</EFSPolicy>
</Batch>
</efs:Request>';
WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' as xsi,
'http://www.slsot.org/efs' AS efs)
SELECT EFS.[Policy].value('(./PolicyNumber/text())[1]','varchar(25)') AS PolicyNumber,
EFS.[Policy].value('(./Coverages/Coverage/#CvgCode)[1]','int') AS CvgCode --Assumes only 1 CvgCode per policy
FROM (VALUES(#XML)) V(X)
CROSS APPLY V.X.nodes('efs:Request/Batch/EFSPolicy') EFS([Policy]);

mssql parse nested xml

I have an xml message that I need to get the test information out of and into a table using a stored procedure.
I've been using this query:
select distinct
'N' as ORIGSTS,
doc1.Samples.value('(ID)[1]', 'nvarchar(20)') as 'SAMPLE_ID',
doc2.Tests.value('(Name)[1]', 'nvarchar(20)') as 'TEST_NAME'
from
#messageXml.nodes('/CDFAOrderMsg/Samples/Sample') as doc1(Samples),
#messageXml.nodes('/CDFAOrderMsg/Samples/Sample/Tests/Test') as doc2(Tests)
where doc1.Samples.value('(ID)[1]', 'nvarchar(20)') = 456
order by 2, 3
The problem is that it returns the sample ID 456 along with all tests listed in the message. I need to be able to extract the test names along with their associated sample Id to insert into a table. Currently, with two samples and three tests each it returns 12 rows when it should only return 6.
How can I make it return a list of all samples along with their respective test names?
Thanks,
Scott
<OrderMsg xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Samples>
<SourceType>Non-Animal</SourceType>
<Sample>
<ID>456</ID>
<Tests>
<Test>
<Name>SPC</Name>
</Test>
<Test>
<Name>COL</Name>
</Test>
<Test>
<Name>ANTI</Name>
</Test>
</Tests>
</Sample>
<Sample>
<ID>457</ID>
<Tests>
<Test>
<Name>HPC</Name>
</Test>
<Test>
<Name>DEL</Name>
</Test>
<Test>
<Name>NVT</Name>
</Test>
</Tests>
</Sample>
</Samples>
</OrderMsg>
Here is a query that gives the expected result using a outer apply function to get the child nodes collection.
DECLARE #x xml
SET #x = '<OrderMsg xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Samples>
<SourceType>Non-Animal</SourceType>
<Sample>
<ID>456</ID>
<Tests>
<Test>
<Name>SPC</Name>
</Test>
<Test>
<Name>COL</Name>
</Test>
<Test>
<Name>ANTI</Name>
</Test>
</Tests>
</Sample>
<Sample>
<ID>457</ID>
<Tests>
<Test>
<Name>HPC</Name>
</Test>
<Test>
<Name>DEL</Name>
</Test>
<Test>
<Name>NVT</Name>
</Test>
</Tests>
</Sample>
</Samples>
</OrderMsg>'
SELECT DISTINCT
'N' AS ORIGSTS,
s.sampleNode.query('ID').value('.', 'nvarchar(20)') AS 'SAMPLE_ID',
t.testNode.query('Test/Name').value('.', 'nvarchar(20)') AS 'TEST_NAME'
FROM #x.nodes('//Samples/Sample') s (sampleNode)
OUTER APPLY (SELECT
x.testNode.query('.') testNode
FROM sampleNode.nodes('Tests/Test') x (testNode)) t
WHERE s.sampleNode.value('(ID)[1]', 'nvarchar(20)') = 456
ORDER BY 2, 3

XQuery parent and child

Example:
DECLARE #XML XML = '
<Items>
<document id="doc1" value="100">
<details>
<detail detailID="1" detailValue="20"/>
<detail detailID="2" detailValue="80"/>
</details>
</document>
<document id="doc2" value="0">
<details>
</details>
</document>
</Items>
'
I want results like this:
id value detailID detailValue
doc1 100 1 20
doc1 100 2 80
doc2 0 NULL NULL
Tried:
SELECT document.value('../../#docID', 'VARCHAR(10)') AS 'docID',
document.value('../../#value', 'INT') AS 'value',
document.value('#detailID', 'VARCHAR(10)') AS 'detailID',
document.value('#detailValue', 'INT') AS 'detailValue'
FROM #XML.nodes('Items/document/details/detail') AS Documents(document)
But, doc2 is not listed... Also, tried with CROSS JOIN and INNER JOIN, but performance is very bad.
Try this:
SELECT document.value('#id', 'VARCHAR(10)') AS docID,
document.value('#value', 'INT') AS value,
Detail.value('#detailID', 'INT') as DetailId,
Detail.value('#detailValue', 'INT') as DetailValue
FROM #XML.nodes('Items/document') AS Documents(document)
outer apply Documents.document.nodes('details/detail') as Details(Detail);
Just one added detail:
#XML.nodes('//whatever_depth') AS Documents(document)
Using '//' Allows you to query not directly from root
Regards,
Dennes

XQuery to combine node values with Group By logic

I’m looking for an XQuery that will take:
<root>
<entity>
<entityid>1</entityid>
<sometext>this is some text</sometext>
</entity>
<entity>
<entityid>1</entityid>
<sometext>this is some more text</sometext>
</entity>
</root>
And produce a recordset like:
Entityid sometext
1 this is some textthis is some more text
Essentially, combining the values in the 'sometext' nodes while grouping by the entityid. I figured I might be able to accomplish this with loops, but wasn't sure if there was a better way, possibly with a join/group by
declare #XML xml =
'<root>
<entity>
<entityid>1</entityid>
<sometext>this is some text</sometext>
</entity>
<entity>
<entityid>1</entityid>
<sometext>this is some more text</sometext>
</entity>
<entity>
<entityid>2</entityid>
<sometext>Another entity</sometext>
</entity>
</root>';
select T.entityid,
#XML.query('/root/entity[entityid = sql:column("T.entityid")]/sometext').value('.', 'nvarchar(max)') as sometext
from (
select distinct T.N.value('entityid[1]', 'int') as entityid
from #XML.nodes('/root/entity') as T(N)
) as T;
Result:
entityid sometext
----------- -----------------------------------------
1 this is some textthis is some more text
2 Another entity
You could also do that using a more XQuery-based solution, eg
DECLARE #xml XML = '<root>
<entity>
<entityid>1</entityid>
<sometext>this is some text</sometext>
</entity>
<entity>
<entityid>1</entityid>
<sometext>this is some more text</sometext>
</entity>
<entity>
<entityid>2</entityid>
<sometext>Another entity</sometext>
</entity>
</root>'
select
x.c.value('#entityId', 'int') entityId,
x.c.value('.', 'varchar(max)') someText
from
(
select #xml.query('for $e in distinct-values(root/entity/entityid)
return <m entityId = "{$e}">{data(root/entity[entityid = $e]/sometext)}</m>')
) r(c)
cross apply r.c.nodes('m') x(c)
Thanks to Mikael for xml / extra scenario.

TSQL FOR XML EXPLICIT

Not able to to get the desired XML output
The following:
SELECT 1 as Tag,
0 as Parent,
sID as [Document!1!sID],
docID as [Document!1!docID],
null as [To!2!value]
FROM docSVsys with (nolock)
where docSVsys.sID = '57'
UNION ALL
SELECT 2 as Tag,
1 as Parent,
sID,
NULL,
value
FROM docMVtext
WHERE docMVtext.sID = '57'
ORDER BY [Document!1!sID],[To!2!value]
FOR XML EXPLICIT;
Produces:
<Document sID="57" docID="3.818919.C41P3UKK00BRICLAY0AR1ET2EBPYSU4SA">
<To value="Frank Ermis" />
<To value="Keith Holst" />
<To value="Mike Grigsby" />
</Document>
What I want is:
<Document sID="57">
<docID>3.818919.C41P3UKK00BRICLAY0AR1ET2EBPYSU4SA</docID>
<To>
<Value>Frank Ermis</Value>
<Value>Keith Holst</Value>
<Value>Mike Grigsby</Value>
</To>
</Document>
Can I get that ouput with FOR XML?
Ok I get they may be technically equivalent.
What I want and what I need are not the same.
Using xDocument for this is is SLOW.
There are millions of documents and need to XML up to 1 million at a time to XML.
The TSQL FOR XML is super fast.
I just need to get FOR XML to format.
The solution (based on accepted answer):
SELECT top 4
[sv].[sID] AS '#sID'
,[sv].[sParID] AS '#sParID'
,[sv].[docID] AS 'docID'
,[sv].addDate as 'addDate'
,(SELECT [value] AS 'value'
FROM [docMVtext] as [mv]
WHERE [mv].[sID] = [sv].[sID]
AND [mv].[fieldID] = '113'
ORDER BY [mv].[value]
FOR XML PATH (''), type
) AS "To"
,(SELECT [value] AS 'value'
FROM [docMVtext] as [mv]
WHERE [mv].[sID] = [sv].[sID]
AND [mv].[fieldID] = '130'
ORDER BY [mv].[value]
FOR XML PATH (''), type
) AS "MVtest"
FROM [docSVsys] as [sv]
WHERE [sv].[sID] >= '57'
ORDER BY
[sv].[sParID], [sv].[sID]
FOR XML PATH('Document'), root('Documents')
Produces:
<Documents>
<Document sID="57" sParID="57">
<docID>3.818919.C41P3UKK00BRICLAY0AR1ET2EBPYSU4SA</docID>
<addDate>2011-10-28T12:26:00</addDate>
<To>
<value>Frank Ermis</value>
<value>Keith Holst</value>
<value>Mike Grigsby</value>
</To>
<MVtest>
<value>MV test 01</value>
<value>MV test 02</value>
<value>MV test 03</value>
<value>MV test 04</value>
</MVtest>
</Document>
<Document sID="58" sParID="57">
<docID>3.818919.C41P3UKK00BRICLAY0AR1ET2EBPYSU4SA.1</docID>
<addDate>2011-10-28T12:26:00</addDate>
</Document>
<Document sID="59" sParID="59">
<docID>3.818920.KJKP5LYKTNIODOEI4JDOKJ2BXJI5P0BIA</docID>
<addDate>2011-10-28T12:26:00</addDate>
<To>
<value>Vladimir Gorny</value>
</To>
</Document>
<Document sID="60" sParID="59">
<docID>3.818920.KJKP5LYKTNIODOEI4JDOKJ2BXJI5P0BIA.1</docID>
<addDate>2011-10-28T12:26:00</addDate>
</Document>
</Documents>
Now what I need to do is to add a DispName attribute to the element MVtext. Attribute cannot have any spaces and I would like to include the friendly name e.g. Multi Value Text.
Try something like this (untested, since I don't have your database tables to test against...):
SELECT
sv.sID AS '#sID',
sv.docID AS 'docID',
(SELECT
value AS 'value'
FROM
dbo.docMVtext mv
WHERE
mv.sID = sv.sID
ORDER BY mv.value
FOR XML PATH (''), TYPE) AS 'To'
FROM
dbo.docSVsys sv
WHERE
sv.sID = '57'
ORDER BY
sv.sID
FOR XML PATH('Document')
Does that give you what you're looking for?? And don't you agree with John and me: this is much simpler than FOR XML EXPLICIT.....
From Examples: Using PATH Mode:
USE AdventureWorks2008R2;
GO
SELECT ProductModelID AS "#ProductModelID",
Name AS "#ProductModelName",
(SELECT ProductID AS "data()"
FROM Production.Product
WHERE Production.Product.ProductModelID =
Production.ProductModel.ProductModelID
FOR XML PATH ('')
) AS "#ProductIDs",
(
SELECT Name AS "ProductName"
FROM Production.Product
WHERE Production.Product.ProductModelID =
Production.ProductModel.ProductModelID
FOR XML PATH (''), type
) AS "ProductNames"
FROM Production.ProductModel
WHERE ProductModelID= 7 OR ProductModelID=9
FOR XML PATH('ProductModelData');

Resources