Cursor for spliting t-sql #xml variable on elements level - sql-server

I need to define some cursor for spliting t-sql #xml variable on elements level into different #xml(s).
for example:
<root>
<element id=10/>
<element id=11/>
<element id=12/>
<element id=13/>
</root>
so that get the following values inside of tsql cursor:
<root><element id=10/><element id=11/></root>
then
<root><element id=12/><element id=13/></root>
and so on where n number of elements pro cursor loop.

Well, you can use the build-in functions for manipulating XML. For example, the following statement:
DECLARE #XML XML = N'<root><element id="10"/><element id="11"/><element id="12"/><element id="13"/></root>'
SELECT ROW_NUMBER() OVER (ORDER BY T.c)
,T.c.query('.')
FROM #XML.nodes('root/element') T(c)
will give you all elements preserving the order they have in the XML structure:
Then you can stored this result and build separate smaller XML variables.
For different elements you can use * like this:
DECLARE #XML XML = N'<root><element1 id="10"/><element2 id="11"/><element3 id="12"/><element4 id="13"/></root>'
SELECT ROW_NUMBER() OVER (ORDER BY T.c)
,T.c.query('.')
FROM #XML.nodes('root/*') T(c)

Related

How to join element values in XQuery for SQL Server?

If I have this XML
<TradingInquirySearchResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SearchTerm>test</SearchTerm>
<CompanyFound>true</CompanyFound>
<CompanyInfoCollection>
<CompanyInfo>
<CompanyID>26</CompanyID>
<CompanyName>test</CompanyName>
<Status>Unrestricted</Status>
<SearchTags>
<Tag>test2</Tag>
<Tag>test3</Tag>
<Tag>test4</Tag>
</SearchTags>
</CompanyInfo>
</CompanyInfoCollection>
</TradingInquirySearchResult>
I want to get "test2;test3;test4". How can I join the values of <Tag> for the first <CompanyInfo> tag?
I tried TransactionData.value('(/TradingInquirySearchResult/CompanyInfoCollection/CompanyInfo[1]/SearchTags/Tag)[1]', 'nvarchar(1000)')
but it didn't work.
Thanks
As you're using SQL Server 2012 (or rather not 2017+) the common way to aggregate strings is to use FOR XML PATH and STUFF, and you can get the multiple values for the node Tag using the nodes operator. This gives you the below:
DECLARE #XML xml = '
<TradingInquirySearchResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SearchTerm>test</SearchTerm>
<CompanyFound>true</CompanyFound>
<CompanyInfoCollection>
<CompanyInfo>
<CompanyID>26</CompanyID>
<CompanyName>test</CompanyName>
<Status>Unrestricted</Status>
<SearchTags>
<Tag>test2</Tag>
<Tag>test3</Tag>
<Tag>test4</Tag>
</SearchTags>
</CompanyInfo>
</CompanyInfoCollection>
</TradingInquirySearchResult>';
SELECT STUFF((SELECT N';' + ST.Tag.value('(./text())[1]','nvarchar(100)')
FROM (VALUES(#XML))V(X)
CROSS APPLY V.X.nodes('/TradingInquirySearchResult/CompanyInfoCollection/CompanyInfo/SearchTags/Tag') ST(Tag)
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,1,N'') AS Tags
Edit for guess when multiple companies.
If you only want the first company, then yes, using [1] would work:
DECLARE #XML xml = '
<TradingInquirySearchResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SearchTerm>test</SearchTerm>
<CompanyFound>true</CompanyFound>
<CompanyInfoCollection>
<CompanyInfo>
<CompanyID>26</CompanyID>
<CompanyName>test</CompanyName>
<Status>Unrestricted</Status>
<SearchTags>
<Tag>test2</Tag>
<Tag>test3</Tag>
<Tag>test4</Tag>
</SearchTags>
</CompanyInfo>
<CompanyInfo>
<CompanyID>27</CompanyID>
<CompanyName>Sample</CompanyName>
<Status>Restricted</Status>
<SearchTags>
<Tag>test6</Tag>
<Tag>test7</Tag>
<Tag>test8</Tag>
</SearchTags>
</CompanyInfo>
</CompanyInfoCollection>
</TradingInquirySearchResult>';
SELECT STUFF((SELECT N';' + ST.Tag.value('(./text())[1]','nvarchar(100)')
FROM (VALUES(#XML))V(X)
CROSS APPLY V.X.nodes('/TradingInquirySearchResult/CompanyInfoCollection/CompanyInfo[1]/SearchTags/Tag') ST(Tag)
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,1,N'') AS Tags;
If, however, you want a row per company, then you would want to do something like this:
SELECT CIC.CI.value('(./CompanyID/text())[1]','nvarchar(50)') AS CompanyID,
STUFF((SELECT N';' + ST.Tag.value('(./text())[1]','nvarchar(100)')
FROM CIC.CI.nodes('./SearchTags/Tag') ST(Tag)
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,1,N'') AS Tags
FROM (VALUES(#XML))V(X)
CROSS APPLY V.X.nodes('/TradingInquirySearchResult/CompanyInfoCollection/CompanyInfo') CIC(CI);

Get XML structure in SQL SERVER

I have an XML variable, for ex.
DECLARE #xml XML =
'<A>
<AA>aa</AA>
<AB>
<ABA>aba</ABA>
</AB>
</A>
<B>b</B>
<C>
<CA>ca</CA>
</C>
I want to get a structure of this XML- table with one VARCHAR kolumn:
structure (VARCHAR)
--------------------
'A/AA'
'A/AB/ABA'
'B'
'C/CA'.
I don't need to get text in node- i need only structure.
XML variable can be different (i don't know number of nodes, name of nodes, etc.).
Variable #xml can be without ROOT element.
I tried many combinations of .value() or .nodes(), but it didn't works.
Best result give me an operation:
SELECT
grandparent.gname.value('fn:local-name(.)', 'VARCHAR(MAX)'),
parent.pname.value('fn:local-name(.)', 'VARCHAR(MAX)'),
child.cname.value('fn:local-name(.)', 'VARCHAR(MAX)')
FROM
#xml.nodes('*') AS grandparent(gname)
CROSS APPLY
grandparent.gname.nodes('*') AS parent(pname)
CROSS APPLY
parent.pname.nodes('*') AS child(cname)
It gaves me 'A/AB/ABA', but if i don't know number of nodes and nodes names, it is useless to me to continue.
Use a recursive CTE to extract the nodes one level at a time. The anchor part extract the root nodes and query('*') gets the child nodes for each node found. exist('*') is used to filter out the intermediate rows that is created during the recursion. The recursive part does the same as the anchor only it uses the XML provided in SubNodes instead.
declare #xml xml =
'<A>
<AA>aa</AA>
<AB>
<ABA>aba</ABA>
</AB>
</A>
<B>b</B>
<C>
<CA>ca</CA>
</C>';
with C as
(
select T.X.value('local-name(.)', 'nvarchar(max)') as Structure,
T.X.query('*') as SubNodes,
T.X.exist('*') as HasSubNodes
from #xml.nodes('*') as T(X)
union all
select C.structure + N'/' + T.X.value('local-name(.)', 'nvarchar(max)'),
T.X.query('*'),
T.X.exist('*')
from C
cross apply C.SubNodes.nodes('*') as T(X)
)
select C.Structure
from C
where C.HasSubNodes = 0;
Result:
Structure
---------
B
C/CA
A/AA
A/AB/ABA

Casting a field to XML, querying it returns NULL records

I've a field on my table that is nvarchar(max) and contains XML document. Don't ask why it is nvarchar(max) instead of XML because I don't know it.
By the way, here is an extraction of a sample XML:
<?xml version="1.0" encoding="utf-16"?>
<ItemType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AutoPay xmlns="urn:ebay:apis:eBLBaseComponents">true</AutoPay>
<Country xmlns="urn:ebay:apis:eBLBaseComponents">IT</Country>
<Currency xmlns="urn:ebay:apis:eBLBaseComponents">EUR</Currency>
<HitCounter xmlns="urn:ebay:apis:eBLBaseComponents">BasicStyle</HitCounter>
<ListingDuration xmlns="urn:ebay:apis:eBLBaseComponents">GTC</ListingDuration>
<ListingType xmlns="urn:ebay:apis:eBLBaseComponents">FixedPriceItem</ListingType>
<Location xmlns="urn:ebay:apis:eBLBaseComponents">Italy</Location>
<PaymentMethods xmlns="urn:ebay:apis:eBLBaseComponents">PayPal</PaymentMethods>
<PayPalEmailAddress xmlns="urn:ebay:apis:eBLBaseComponents">email#paypal.com</PayPalEmailAddress>
<PrimaryCategory xmlns="urn:ebay:apis:eBLBaseComponents">
<CategoryID>137084</CategoryID>
</PrimaryCategory>
<ShippingDetails xmlns="urn:ebay:apis:eBLBaseComponents">
<ShippingServiceOptions>
<ShippingService>StandardShippingFromOutsideUS</ShippingService>
<ShippingServiceCost currencyID="EUR">0</ShippingServiceCost>
<ShippingServiceAdditionalCost currencyID="EUR">0</ShippingServiceAdditionalCost>
<FreeShipping>true</FreeShipping>
</ShippingServiceOptions>
<InternationalShippingServiceOption>
<ShippingService>StandardInternational</ShippingService>
<ShippingServiceCost currencyID="EUR">0</ShippingServiceCost>
<ShippingServiceAdditionalCost currencyID="EUR">0</ShippingServiceAdditionalCost>
<ShippingServicePriority>1</ShippingServicePriority>
<ShipToLocation>Americas</ShipToLocation>
<ShipToLocation>Europe</ShipToLocation>
</InternationalShippingServiceOption>
<ShippingType>Flat</ShippingType>
<InsuranceDetails>
<InsuranceFee currencyID="EUR">0</InsuranceFee>
<InsuranceOption>NotOffered</InsuranceOption>
</InsuranceDetails>
<InternationalInsuranceDetails>
<InsuranceFee currencyID="EUR">0</InsuranceFee>
<InsuranceOption>NotOffered</InsuranceOption>
</InternationalInsuranceDetails>
</ShippingDetails>
<Site xmlns="urn:ebay:apis:eBLBaseComponents">US</Site>
<Storefront xmlns="urn:ebay:apis:eBLBaseComponents">
<StoreCategoryID>2947535016</StoreCategoryID>
<StoreCategory2ID>0</StoreCategory2ID>
</Storefront>
<DispatchTimeMax xmlns="urn:ebay:apis:eBLBaseComponents">4</DispatchTimeMax>
<ReturnPolicy xmlns="urn:ebay:apis:eBLBaseComponents">
<ReturnsAcceptedOption>ReturnsAccepted</ReturnsAcceptedOption>
<Description>Accepted</Description>
<ShippingCostPaidByOption>Buyer</ShippingCostPaidByOption>
</ReturnPolicy>
<ConditionID xmlns="urn:ebay:apis:eBLBaseComponents">1000</ConditionID>
</ItemType>
I would love to query the table on that field, for example to extract CategoryID field.
I tried everything I knew, like casting to ntext, removing utf-16, replacing it with utf-8, adding namespaces and stuff like that, but the result is always a NULL record.
Here is one of the queries I tried:
;WITH XMLNAMESPACES('urn:ebay:apis:eBLBaseComponents' AS ns,
'http://www.w3.org/2001/XMLSchema-instance' as xsi,
'http://www.w3.org/2001/XMLSchema' as xsd)
select CategoryVal = CONVERT(xml, [Template]).value('(/ItemType/PrimaryCategory/CategoryID)[1]', 'nvarchar(max)') FROM Templates where ID = 1
Thanks, Marco
with xmlnamespaces('urn:ebay:apis:eBLBaseComponents' as n)
select cast(Template as xml).value('(/ItemType/n:PrimaryCategory/n:CategoryID)[1]', 'nvarchar(max)')
from Templates
where ID = 1
You need to prefix the elements in your xpath expression.
I've done that once, but without the use of namespaces.
I had my input as varchar(max) (nvarchar should work too)
#Text AS varchar(MAX)
Then i used an XML type variable and the conversion was as simple as this:
DECLARE #XML XML
SELECT #XML = #Text
To query your CategoryID value you would use:
SELECT itemtype.item.value('(/ItemType/PrimaryCategory/CategoryID)[1]', 'nvarchar(max)')
FROM #XML.nodes('/ItemType') AS itemtype(item);

Read value in XML Node - T-SQL

This is my code.......
DECLARE #XML AS XML;
SET #XML = CAST('<Session id="ID969138672" realTimeID="4300815712">
<VarValues>
<varValue id="ID123" source="Internal" name="DisconnectedBy">VisitorClosedWindow</varValue>
<varValue id="ID1234" source="PreChat" name="email">1234#mail.ru</varValue>
</VarValues>
</Session>
' AS XML)
SELECT
xmlData.Col.value('#id','varchar(max)')
,xmlData.Col.value('#source','varchar(max)')
,xmlData.Col.value('#name','varchar(max)')
FROM #XML.nodes('//Session/VarValues/varValue') xmlData(Col);
This is the output.....
How can I include the actual values of the varValue?
I need to read the values VisistorClosedWindow and 1234#mail.ru values as well
You can get that by doing this:
xmlData.Col.value('.','varchar(max)')
So the select would be:
SELECT
xmlData.Col.value('#id','varchar(max)')
,xmlData.Col.value('#source','varchar(max)')
,xmlData.Col.value('#name','varchar(max)')
,xmlData.Col.value('.','varchar(max)')
FROM #XML.nodes('//Session/VarValues/varValue') xmlData(Col);
Just use the .value('.', 'varchar(50)) line for that:
SELECT
xmlData.Col.value('#id','varchar(25)'),
xmlData.Col.value('#source','varchar(50)'),
xmlData.Col.value('#name','varchar(50)'),
xmlData.Col.value('.','varchar(50)') -- <== this gets your the element's value
FROM #XML.nodes('//Session/VarValues/varValue') xmlData(Col);

Concatenate XML without type casting to string

I have the following XML generated from various tables in my SQL SERVER database
<XMLData>
...
<Type>1</Type>
...
</XMLData>
AND
<XMLData>
...
<Type>2</Type>
...
</XMLData>
AND
<XMLData>
...
<Type>3</Type>
...
</XMLData>
The final output I need is single combined as follows:
<AllMyData>
<XMLData>
...
<Type>1</Type>
...
</XMLData>
<XMLData>
...
<Type>2</Type>
...
</XMLData>
<XMLData>
...
<Type>3</Type>
...
</XMLData>
<AllMyData>
NOTE - all the independent elements that I am combining have the same tag name.
Thanks in advance for looking this up.
I have the following XML generated from various tables in my SQL
SERVER database
Depends on how you have it but if it is in a XML variable you can do like this.
declare #XML1 xml
declare #XML2 xml
declare #XML3 xml
set #XML1 = '<XMLData><Type>1</Type></XMLData>'
set #XML2 = '<XMLData><Type>2</Type></XMLData>'
set #XML3 = '<XMLData><Type>3</Type></XMLData>'
select #XML1, #XML2, #XML3
for xml path('AllMyData')
I can't comment but can answer so even though I think a comment is more appropriate, I'll expand on what rainabba answered above to add a bit more control. My .Net code needs to know the column name returned so I can't rely on auto-generated names but needed the very tip rainabba provided above otherwise.
This way, the xml can effectively be concatenated into a single row and the resulting column named. You could use this same approach to assign the results to an XML variable and return that from a PROC also.
SELECT (
SELECT XmlData as [*]
FROM
(
SELECT
xmlResult AS [*]
FROM
#XmlRes
WHERE
xmlResult IS NOT NULL
FOR XML PATH(''), TYPE
) as DATA(XmlData)
FOR XML PATH('')
) as [someColumnName]
If you use for xml type, you can combine the XML columns without casting them. For example:
select *
from (
select (
select 1 as Type
for xml path(''), type
)
union all
select (
select 2 as Type
for xml path(''), type
)
union all
select (
select 3 as Type
for xml path(''), type
)
) as Data(XmlData)
for xml path(''), root('AllMyData'), type
This prints:
<AllMyData>
<XmlData>
<Type>1</Type>
</XmlData>
<XmlData>
<Type>2</Type>
</XmlData>
<XmlData>
<Type>3</Type>
</XmlData>
</AllMyData>
As an addendum to Mikael Eriksson's answer - If you have a process where you need to continually add nodes and then want to group that under a single node, this is one way to do it:
declare #XML1 XML
declare #XML2 XML
declare #XML3 XML
declare #XMLSummary XML
set #XML1 = '<XMLData><Type>1</Type></XMLData>'
set #XMLSummary = (SELECT #XMLSummary, #XML1 FOR XML PATH(''))
set #XML2 = '<XMLData><Type>2</Type></XMLData>'
set #XMLSummary = (SELECT #XMLSummary, #XML2 FOR XML PATH(''))
set #XML3 = '<XMLData><Type>3</Type></XMLData>'
set #XMLSummary = (SELECT #XMLSummary, #XML3 FOR XML PATH(''))
SELECT #XMLSummary FOR XML PATH('AllMyData')
I needed to do the same but without knowing how many rows/variables were concerned and without extra schema added so here was my solution. Following this pattern, I can generate as many snippets as I want, combine them, pass them between PROCS or even return them from procs and at any point, wrap them up in containers all without modifying the data or being forced to add XML structure into my data. I use this approach with HTTP end points to provide XML Web services and with another trick that converts XML into JSON, to provide JSON WebServices.
-- SETUP A type (or use this design for a Table Variable) to temporarily store snippets into. The pattern can be repeated to pass/store snippets to build
-- larger elements and those can be further combined following the pattern.
CREATE TYPE [dbo].[XMLRes] AS TABLE(
[xmlResult] [xml] NULL
)
GO
-- Call the following as much as you like to build up all the elements you want included in the larger element
INSERT INTO #XMLRes ( xmlResult )
SELECT
(
SELECT
'foo' '#bar'
FOR XML
PATH('SomeTopLevelElement')
)
-- This is the key to "concatenating" many snippets into a larger element. At the end of this, add " ,ROOT('DocumentRoot') " to wrapp them up in another element even
-- The outer select is a time from user2503764 that controls the output column name
SELECT (
SELECT XmlData as [*]
FROM
(
SELECT
xmlResult AS [*]
FROM
#XmlRes
WHERE
xmlResult IS NOT NULL
FOR XML PATH(''), TYPE
) as DATA(XmlData)
FOR XML PATH('')
) as [someColumnName]
ALTER PROCEDURE usp_fillHDDT #Code int
AS
DECLARE #HD XML,#DT XML;
SET NOCOUNT ON;
select invhdcode, invInvoiceNO,invDate,invCusCode,InvAmount into #HD
from dbo.trnInvoiceHD where invhdcode=#Code
select invdtSlNo No,invdtitemcode ItemCode,invdtitemcode ItemName,
invDtRate Rate,invDtQty Qty,invDtAmount Amount ,'Kg' Unit into #DT from
dbo.trnInvoiceDt where invDtTrncode=#Code
set #HD = (select * from #HD HD FOR XML AUTO,ELEMENTS XSINIL);
set #DT = (select* from #DT DT FOR XML AUTO,ELEMENTS XSINIL);
SELECT CAST ('<OUTPUT>'+ CAST (ISNULL(#HD,'') AS VARCHAR(MAX))+ CAST ( ISNULL(#DT,'') AS VARCHAR(MAX))+ '</OUTPUT>' AS XML)
public String ReplaceSpecialChar(String inStr)
{
inStr = inStr.Replace("&", "&");
inStr = inStr.Replace("<", "<");
inStr = inStr.Replace(">", ">");
inStr = inStr.Replace("'", "'");
inStr = inStr.Replace("\"", """);
return inStr;
}

Resources