XML PATH in SQL SERVER how to NEST - sql-server

I'm new to using XML and I think this is an easy question, but i just can't get the format correct.
I'm trying to get this output
<Contracts>
<Contract>
<Ref>P000006-140</Ref>
<ReportingTransactionAmountEntry>
<TechAccountAmtItem>
<Amt Ccy="GBP">7500.0000</Amt>
</TechAccountAmtItem>
</ReportingTransactionAmountEntry>
</Contract>
<Contract>
<Ref>P000006-140</Ref>
<ReportingTransactionAmountEntry>
<TechAccountAmtItem>
<Amt Ccy="GBP">100.0000</Amt>
</TechAccountAmtItem>
<TechAccountAmtItem>
<Amt Ccy="GBP">7600.0000</Amt>
</TechAccountAmtItem>
</ReportingTransactionAmountEntry>
</Contract>
<Contract>
<Ref>P000006-140</Ref>
<ReportingTransactionAmountEntry>
<TechAccountAmtItem>
<Amt Ccy="EUR">500.0000</Amt>
</TechAccountAmtItem>
</ReportingTransactionAmountEntry>
</Contract>
</Contracts>
to look like this,
with the contact Reference only showing once at a higher level
<Contracts>
<Contract>
<Ref>P000006-140</Ref>
<ReportingTransactionAmountEntry>
<TechAccountAmtItem>
<Amt Ccy="GBP">7500.0000</Amt>
</TechAccountAmtItem>
</ReportingTransactionAmountEntry>
<ReportingTransactionAmountEntry>
<TechAccountAmtItem>
<Amt Ccy="GBP">100.0000</Amt>
</TechAccountAmtItem>
<TechAccountAmtItem>
<Amt Ccy="GBP">7600.0000</Amt>
</TechAccountAmtItem>
</ReportingTransactionAmountEntry>
<ReportingTransactionAmountEntry>
<TechAccountAmtItem>
<Amt Ccy="EUR">500.0000</Amt>
</TechAccountAmtItem>
</ReportingTransactionAmountEntry>
</Contract>
</Contracts>
my query looks like this
SELECT
RTRIM(PolicyRef) AS 'Ref',
(SELECT * FROM
(
select
RTRIM(SettlementCurrency) AS 'TechAccountAmtItem/Amt/#Ccy',
Paid_This_Time_Indemnity AS 'TechAccountAmtItem/Amt'
UNION
SELECT
RTRIM(SettlementCurrency) AS 'TechAccountAmtItem/Amt/#Ccy',
Paid_To_Date_Indemnity AS 'TechAccountAmtItem/Amt'
) iq FOR XML PATH(''),TYPE
) AS 'ReportingTransactionAmountEntry'
FROM #tmpClm3
FOR XML PATH ('Contract'),ROOT('Contracts')
any help would be greatly appreciated.

You need to group on PolicyRef in the main query and get the details in a sub-query.
select T1.PolicyRef as [Ref],
(
select
(
select T2.SettlementCurrency as [TechAccountAmtItem/Amt/#Ccy],
T2.Paid_This_Time_Indemnity as [TechAccountAmtItem/Amt]
where T2.Paid_This_Time_Indemnity is not null
for xml path(''), type
),
(
select T2.SettlementCurrency as [TechAccountAmtItem/Amt/#Ccy],
T2.Paid_To_Date_Indemnity as [TechAccountAmtItem/Amt]
where T2.Paid_To_Date_Indemnity is not null
for xml path(''), type
)
from T as T2
where T1.PolicyRef = T2.PolicyRef
for xml path('ReportingTransactionAmountEntry'), type
)
from T as T1
group by T1.PolicyRef
for xml path('Contract'), root('Contracts')
SQL Fiddle

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]);

Converting Every Child Tags in to a Single Column with multiple Delimiters -SQL Server (3)

My xml:
declare #x xml='<Detail>
<ROll ID="1">
<Exams>
<Examdetails date="2017-04-02 13:30:00">
<Exam name="ECO" Total="100">150</Exam>
<Exam name="BIO" Total="150">50</Exam>
<Exam name="MATH" Total="200">28</Exam>
</Examdetails>
<Examdetails date="2017-04-02 14:30:00">
<Exam name="ENGLISH" Total="100">150</Exam>
<Exam name="BIO" Total="200">50</Exam>
<Exam name="ZIO" Total="250">28</Exam>
</Examdetails>
</Exams>
</ROll>
<ROll ID="2">
<Exams>
<Examdetails date="2017-05-02 13:30:00">
<Exam name="HIS" Total="100">150</Exam>
<Exam name="BIO" Total="200">50</Exam>
<Exam name="THI" Total="200">89</Exam>
</Examdetails>
</Exams>
</ROll>
</Detail>'
I want to Segregate my xml based on ROLL ID's while i tried with the below query referred from here
SELECT STUFF(
(
SELECT '!' + STUFF(p.query(N'for $n in .//*
return <a>{concat("$",($n/text())[1])}</a>'
).value(N'.',N'nvarchar(max)'),1,1,'')
FROM p.nodes(N'Exams') AS A(p)
FOR XML PATH(''),TYPE).value(N'.',N'nvarchar(max)'),1,1,'')
FROM #x.nodes(N'Detail/ROll') AS A(p);
I get the result as
But i want to query it back as
2017-04-02 13:30:00$ECO$100$150!2017-04-02 13:30:00$BIO$150$50!2017-04-02 13:30:00$MATH$200$28!2017-04-02 14:30:00$ENGLISH$100$150!2017-04-02 14:30:00$BIO$200$50!2017-04-02 14:30:00$ZIO$250$28
2017-05-02 13:30:00$HIS$100$150!2017-05-02 13:30:00$BIO$200$50!2017-05-02 13:30:00$THI$200$89
Kindly help me solve this complexity
Thanks in advance ,Jayendran
In this case I'd leave the generical path and build it up like this:
SELECT r.value(N'#ID',N'int') AS ROll_ID
,STUFF((
SELECT
(
SELECT '!'+ed.value(N'#date',N'nvarchar(max)')
+'$' + e.value(N'#name','nvarchar(max)')
+'$' + e.value(N'#Total','nvarchar(max)')
+'$' + e.value(N'text()[1]','nvarchar(max)')
FROM ed.nodes(N'Exam') AS D(e)
FOR XML PATH(''),TYPE
).value(N'text()[1]','nvarchar(max)')
FROM ex.nodes(N'Examdetails') AS C(ed)
FOR XML PATH(''),TYPE
).value(N'text()[1]','nvarchar(max)'),1,1,'')
FROM #x.nodes(N'/Detail/ROll') AS A(r)
CROSS APPLY r.nodes(N'Exams') AS B(ex);
Main issues why a generic solution might not work:
it is difficult to mix attributes and element's text() generically
(important!) the attributes order is not guaranteed! Attribute values might appear not in order expected...

How to fetch all data from XML column in SQL Server

I am trying to fetch the data from a XML column. This is the query and below it is the xml column data
SELECT
[PQDAdvice].value('(/Advice//DORowData/PrimaryValue/node())[1]', 'nvarchar(max)') as PrimaryValue,
[PQDAdvice].value('(/Advice//DORowData/ListValue1/node())[1]', 'nvarchar(max)') as ListValue1
FROM
PatQD
WHERE
PQDPatID = '4c983bd8-da00-4395-80bb-a383b21313d5'
XML contents:
<Advice>
<DORowData>
<PrimaryValue>Diet and Nutrition</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
<DORowData>
<PrimaryValue>Salt Restriction</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
<DORowData>
<PrimaryValue>Water Consumption</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
</Advice>
The issue I am getting that only one column I am getting instead of all column
Primary |ListValue1
Diet and Nutrition |Advice
Instead of
Primary |ListValue1
Diet and Nutrition |Advice
Salt Restriction |Advice
Water Consumption |Advice
DECLARE #xml XML = '
<Advice>
<DORowData>
<PrimaryValue>Diet and Nutrition</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
<DORowData>
<PrimaryValue>Salt Restriction</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
<DORowData>
<PrimaryValue>Water Consumption</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
</Advice>'
SELECT
t.c.value('PrimaryValue[1]', 'nvarchar(max)') as PrimaryValue,
t.c.value('ListValue1[1]', 'nvarchar(max)') as ListValue1
FROM #xml.nodes('Advice/DORowData') t(c)
Output -
PrimaryValue ListValue1
----------------------- ------------
Diet and Nutrition Advice
Salt Restriction Advice
Water Consumption Advice
Your query -
SELECT
PrimaryValue = t.c.value('PrimaryValue[1]', 'NVARCHAR(1000)'),
ListValue1 = t.c.value('ListValue1[1]', 'NVARCHAR(1000)')
FROM PatQD
CROSS APPLY PQDAdvice.nodes('Advice/DORowData') t(c)
where PQDPatID = '4c983bd8-da00-4395-80bb-a383b21313d5'
Try it like this
DECLARE #x XML=
'<Advice>
<DORowData>
<PrimaryValue>Diet and Nutrition</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
<DORowData>
<PrimaryValue>Salt Restriction</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
<DORowData>
<PrimaryValue>Water Consumption</PrimaryValue>
<ListValue1>Advice</ListValue1>
</DORowData>
</Advice>';
SELECT DoRowData.value('PrimaryValue[1]','varchar(max)') AS PrimaryValue
,DoRowData.value('ListValue1[1]','varchar(max)') AS ListValue1
FROM #x.nodes('/Advice/DORowData') AS One(DORowData)

TSQL CTE hierarchy

I am trying to create a CTE on my table to pull in a hierarchy of employees.
I have a starting point which is a "Director" and I want to find everyone that reports to each person under them.
Here is what I have so far:
;WITH EmpTable_CTE (FirstName, LastName, QID, Email) AS
(
SELECT FirstName,
LastName,
QID,
Email
FROM EmployeeTable E
WHERE QID = '12345'
UNION ALL
SELECT E.FirstName,
E.LastName,
E.QID,
E.Email
FROM EmployeeTable E
INNER JOIN EmpTable_CTE AS E2 ON E.MgrQID = E2.QID
)
SELECT * FROM EmpTable_CTE
This seems to work providing me a list of employees but there is no "hierarchy" to it.
How can I go about using FOR XML to create the hierarchy that I am looking for?
<Director>Bob Smith</Director>
<Direct>Jim Smith</Direct>
<Direct>Employee 1</direct>
<Direct>Employee 2</direct>
<Direct>Employee 3</direct>
<Direct>Bob Jones</Direct>
<Direct>Employee 1</direct>
<Direct>Employee 2</direct>
<Direct>Employee 3</direct>
<Direct>Employee A</direct>
I'm sure its just a matter of placing the FOR XML line somewhere but cant quite figure it out.
Update: Here is a SQL Fiddle of sample data:
http://sqlfiddle.com/#!6/a48f6/1
This is how I would expect the data to be from the fiddle:
<Director>Jim Jones</Director>
<Direct>Bob Jones</Direct>
<Direct>Jake Jones</Direct>
<Direct>Smith Jones</Direct>
<Direct>Carl Jones</Direct>
<Direct>Bobby Jones</Direct>
<Direct>Danny Jones</Direct>
<Direct>Billy Jones</Direct>
Part of the difficulty is in the XML structure you presented - If you passed that into a parser, it would all be flat, and running the results of my process below without stuffing the First and Last name into an attribute made the nodes come out in mixed content (text with nodes on the same level).
So, I went searching and found this little gem here on SE. Adapting it to your needs, and throwing in a few fields as attributes from your table, I came up with this:
CREATE FUNCTION dbo.EmpHierarchyNode(#QID int)
RETURNS XML
WITH RETURNS NULL ON NULL INPUT
BEGIN RETURN
(SELECT QID AS "#ID", Email AS "#Email",
FirstName + ' ' + LastName AS "#Name",
CASE WHEN MgrQID = #QID
THEN dbo.EmpHierarchyNode(QID)
END
FROM dbo.EmployeeTable
WHERE MgrQID = #QID
FOR XML PATH('Direct'), TYPE)
END
SELECT QID AS "#ID", Email AS "#Email",
FirstName + ' ' + LastName AS "#Name",
dbo.EmpHierarchyNode(QID)
FROM dbo.EmployeeTable
WHERE MgrQID IS NULL
FOR XML PATH('Director'), TYPE
Essentially, this traverses down in the hierarchy, recursively calling itself. The CTE isn't sufficient if your output is targeted for XML. Using this, and what I could glean of your sample data, I got this as a result:
<Director ID="1" Email="bsmith#someCompany.com" Name="Bob Smith">
<Direct ID="2" Email="jsmith#someCompany.com" Name="Jim Smith">
<Direct ID="4" Email="e1#someCompany.com" Name="Employee 1" />
<Direct ID="5" Email="e2#someCompany.com" Name="Employee 2" />
<Direct ID="7" Email="e4#someCompany.com" Name="Employee 4" />
</Direct>
<Direct ID="3" Email="bjones#someCompany.com" Name="Bob Jones">
<Direct ID="6" Email="e3#someCompany.com" Name="Employee 3" />
<Direct ID="8" Email="e5#someCompany.com" Name="Employee 5" />
<Direct ID="9" Email="e6#someCompany.com" Name="Employee 6" />
</Direct>
</Director>
Hope this helps.
EDIT: Last Minute SQLFiddle Example.
See if it meets yours requirement:
;WITH EmpTable_CTE (FirstName, LastName, QID, Email) AS
(
SELECT FirstName,
LastName,
QID,
Email
FROM EmployeeTable E
WHERE QID = 1
UNION ALL
SELECT E.FirstName,
E.LastName,
E.QID,
E.Email
FROM EmployeeTable E
INNER JOIN EmpTable_CTE AS E2
ON E.MgrQID = E2.QID
)
SELECT LastName + ', ' + FirstName FROM EmpTable_CTE FOR XML PATH('Direct'), ROOT('Director'), TYPE

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