count occurrence of xml child nodes from two identical parent nodes - sql

I'm trying to count the number child nodes within two identical parent nodes in an XML clob that I've stored in a table of mine.
The XML Clob has a format like this:
<ProductS>
<id>1</id>
<Discount></Discount>
<Discount></Discount>
</ProductS>
<ProductS>
<id>2</id>
<Discount></Discount>
</ProductS>
I want my xmlquery to go through this XML clob and identify how many Discounts there are in each ProductS and IGNORE those that have less or zero <Discount>.
So in the example above it should return 2.
Thanks.

I just realized that my answer is for SQL-SERVER only. I'll let it here in case this help other people
Here is a Query that will get you 1 record for each product with 1 or more Discount node. This is done by using the xml query function "count()"
declare #xml xml
set #xml = '<ProductS>
<id>1</id>
<Discount></Discount>
<Discount></Discount>
</ProductS>
<ProductS>
<id>2</id>
<Discount></Discount>
</ProductS>'
select
n.xmlNode.query('.') ProductNode
from #xml.nodes('/ProductS') n(xmlNode)
where
n.xmlNode.query('count(Discount)').value('.','int') > 0

Related

Add data to result of FOR XML PATH SQL SERVER

I have a query that produces XML, exactly as I want it:
SELECT *
FROM [staging].[vw_mydata]
FOR XML PATH('DOCUMENT')
This code produces this output:
<DOCUMENT>
<BRON>Bulk Import</BRON>
<INDEXEERDATUM>2015-07-30</INDEXEERDATUM>
<ITEMVALUE1>ITEMVALUE1</ITEMVALUE1>
<ITEMVALUE2>ITEMVALUE2</ITEMVALUE2>
<ITEMVALUE3>ITEMVALUE3</ITEMVALUE3>
<ITEMVALUE4>ITEMVALUE4</ITEMVALUE4>
<ITEMVALUE5>ITEMVALUE5</ITEMVALUE5>
<ITEMVALUE6>2015-07-30</ITEMVALUE6>
<ITEMVALUE7>ITEMVALUE7</ITEMVALUE7>
<ITEMVALUE8></ITEMVALUE8>
<ITEMVALUE9></ITEMVALUE9>
<ITEMVALUE10></ITEMVALUE10>
<ITEMVALUE11>ITEMVALUE11</ITEMVALUE11>
</DOCUMENT>
<BRON>Bulk Import</BRON>
<INDEXEERDATUM>2015-07-30</INDEXEERDATUM>
<ITEMVALUE1>ITEMVALUE1</ITEMVALUE1>
<ITEMVALUE2>ITEMVALUE2</ITEMVALUE2>
<ITEMVALUE3>ITEMVALUE3</ITEMVALUE3>
<ITEMVALUE4>ITEMVALUE4</ITEMVALUE4>
<ITEMVALUE5>ITEMVALUE5</ITEMVALUE5>
<ITEMVALUE6>2015-07-30</ITEMVALUE6>
<ITEMVALUE7>ITEMVALUE7</ITEMVALUE7>
<ITEMVALUE8></ITEMVALUE8>
<ITEMVALUE9></ITEMVALUE9>
<ITEMVALUE10></ITEMVALUE10>
<ITEMVALUE11>ITEMVALUE11</ITEMVALUE11>
</DOCUMENT>
However, I need to add information before and after this result.
The following has to preceed the results from the query. This part is fixed and does not change.
<?xml version="1.0"?>
<DOCUMENTS>
<VERSION>2.0</VERSION>
<LICENTIEHOUDER>Henrov</LICENTIEHOUDER>
<XTN>BulkImport</XTN>
<ARCHIEFNAAM>Name</ARCHIEFNAAM>
<ITEMLABEL1>Label1</ITEMLABEL1>
<ITEMLABEL2>Label2</ITEMLABEL2>
<ITEMLABEL3>Label3</ITEMLABEL3>
<ITEMLABEL4>Label4</ITEMLABEL4>
<ITEMLABEL5>Label5</ITEMLABEL5>
<ITEMLABEL6>Label6</ITEMLABEL6>
<ITEMLABEL7>Label7</ITEMLABEL7>
<ITEMLABEL8>Label8</ITEMLABEL8>
<ITEMLABEL9>Label9</ITEMLABEL9>
<ITEMLABEL10>Label0</ITEMLABEL10>
<ITEMLABEL11>Label1</ITEMLABEL11>
<ITEMLABEL12>Label2</ITEMLABEL12>
<ITEMLABEL13>Label3</ITEMLABEL13>
<ITEMLABEL14>Label4</ITEMLABEL14>
<ITEMLABEL15>Label5</ITEMLABEL15>
<ITEMLABEL16>Label6</ITEMLABEL16>
<ITEMLABEL17>Label7</ITEMLABEL17>`
</DOCUMENTS> needs to be added to the end so that the whole result looks like this:
<?xml version="1.0"?>
<DOCUMENTS>
<VERSION>2.0</VERSION>
<LICENTIEHOUDER>Henrov</LICENTIEHOUDER>
<XTN>BulkImport</XTN>
<ARCHIEFNAAM>Name</ARCHIEFNAAM>
<ITEMLABEL1>Label1</ITEMLABEL1>
<ITEMLABEL2>Label2</ITEMLABEL2>
<ITEMLABEL3>Label3</ITEMLABEL3>
<ITEMLABEL4>Label4</ITEMLABEL4>
<ITEMLABEL5>Label5</ITEMLABEL5>
<ITEMLABEL6>Label6</ITEMLABEL6>
<ITEMLABEL7>Label7</ITEMLABEL7>
<ITEMLABEL8>Label8</ITEMLABEL8>
<ITEMLABEL9>Label9</ITEMLABEL9>
<ITEMLABEL10>Label0</ITEMLABEL10>
<ITEMLABEL11>Label1</ITEMLABEL11>
<ITEMLABEL12>Label2</ITEMLABEL12>
<ITEMLABEL13>Label3</ITEMLABEL13>
<ITEMLABEL14>Label4</ITEMLABEL14>
<ITEMLABEL15>Label5</ITEMLABEL15>
<ITEMLABEL16>Label6</ITEMLABEL16>
<ITEMLABEL17>Label7</ITEMLABEL17>
<DOCUMENT>
<BRON>Bulk Import</BRON>
<INDEXEERDATUM>2015-07-30</INDEXEERDATUM>
<ITEMVALUE1>ITEMVALUE1</ITEMVALUE1>
<ITEMVALUE2>ITEMVALUE2</ITEMVALUE2>
<ITEMVALUE3>ITEMVALUE3</ITEMVALUE3>
<ITEMVALUE4>ITEMVALUE4</ITEMVALUE4>
<ITEMVALUE5>ITEMVALUE5</ITEMVALUE5>
<ITEMVALUE6>2015-07-30</ITEMVALUE6>
<ITEMVALUE7>ITEMVALUE7</ITEMVALUE7>
<ITEMVALUE8></ITEMVALUE8>
<ITEMVALUE9></ITEMVALUE9>
<ITEMVALUE10></ITEMVALUE10>
<ITEMVALUE11>ITEMVALUE11</ITEMVALUE11>
</DOCUMENT>
<BRON>Bulk Import</BRON>
<INDEXEERDATUM>2015-07-30</INDEXEERDATUM>
<ITEMVALUE1>ITEMVALUE1</ITEMVALUE1>
<ITEMVALUE2>ITEMVALUE2</ITEMVALUE2>
<ITEMVALUE3>ITEMVALUE3</ITEMVALUE3>
<ITEMVALUE4>ITEMVALUE4</ITEMVALUE4>
<ITEMVALUE5>ITEMVALUE5</ITEMVALUE5>
<ITEMVALUE6>2015-07-30</ITEMVALUE6>
<ITEMVALUE7>ITEMVALUE7</ITEMVALUE7>
<ITEMVALUE8></ITEMVALUE8>
<ITEMVALUE9></ITEMVALUE9>
<ITEMVALUE10></ITEMVALUE10>
<ITEMVALUE11>ITEMVALUE11</ITEMVALUE11>
</DOCUMENT>
</DOCUMENTS>
This needs to be exported as a XML file.
I tried to do SELECT 'text in front' + (select for xml) + 'text after' but that has an unwanted result: all < and > are replaced with &lt en &gt effectively destroying my xml.
So basically: how do I add information to the beginning and the end of the result of a FOR XML query?
Simply create a sub query for documents. You'll need to use type to prevent XML tags escaping.
SELECT
'2.0' AS [VERSION],
'Henrov' AS [LICENTIEHOUDER],
-- Add all other fields.
(
SELECT *
FROM [staging].[vw_mydata]
FOR XML PATH('DOCUMENT'), type
)
FOR XML PATH('DOCUMENTS')

Read XML file to datatables using c# asp.net

I want to read XML file and bulk copy to database
My xml is like :
<products>
<product>
<id>1</id>
<name>product one</name>
<subproducts>
<subproduct>
<color>Red</color>
<stock>1</stock>
</subproduct>
<subproduct>
<color>Green</color>
<stock>2</stock>
</subproduct>
</subproducts>
<images>
<image>http://qwqeq.com</image>
<image>http://asdasd.com</image>
</images>
</product>
</products>
I want to get three datatables which are for products, subproducts and images and will try to bulk insert to the database.
How can i aschieve this ?
The way you have tried must be something like below,
DataSet objDataSet = new DataSet();
objDataSet.ReadXml("FilePath.xml");
It just groups the nodes and forms tables.
You need to do something which is explained well in the below link.
http://csharp.net-informations.com/xml/how-to-read-xml.htm
Hope this helps!
You can pass the XML into a stored procedure as XML parameter and shredd it there:
DECLARE #xml XML=
N'<products>
<product>
<id>1</id>
<name>product one</name>
<subproducts>
<subproduct>
<color>Red</color>
<stock>1</stock>
</subproduct>
<subproduct>
<color>Green</color>
<stock>2</stock>
</subproduct>
</subproducts>
<images>
<image>http://qwqeq.com</image>
<image>http://asdasd.com</image>
</images>
</product>
</products>';
SELECT p.value(N'id[1]',N'int') AS productID
,p.value(N'name[1]',N'nvarchar(max)') AS productName
,sp.value(N'color[1]','nvarchar(max)') AS subproductColor
,sp.value(N'stock[1]','int') AS subproductStock
,img.value(N'image[1]',N'nvarchar(max)') AS imageURL
--INTO #tmpTbl
FROM #xml.nodes(N'/products/product') AS A(p)
OUTER APPLY p.nodes(N'subproducts/subproduct') AS B(sp)
OUTER APPLY p.nodes(N'images') AS C(img)
The result
productID productName subproductColor subproductStock imageURL
1 product one Red 1 http://qwqeq.com
1 product one Green 2 http://qwqeq.com
Use SELECT ... INTO #tmpTbl to write the result into a staging table. Then use SELECT DISTINCT ... FROM #tmpTbl to retrieve the values for your insertion into the final structure.

How to convert nested XML into corresponding tables?

I have a complex nested XML (generated from a C# entity graph), for example:
<Customers>
<Customer>
<Id>1</Id>
<Number>12345</Number>
<Addresses>
<Address>
<Id>100</Id>
<Street>my street </street>
<city>London</city>
</Address>
<Address>
<Id>101</Id>
<street>my street 2</street>
<city>Berlin</city>
</Address>
</Addresses>
<BankDetails>
<BankDetail>
<Id>222</Id>
<Iban>DE8439834934939434333</Iban>
</BankDetail>
<BankDetail>
<Id>228</Id>
<Iban>UK1237921391239123213</Iban>
</BankDetail>
</BankDetails>
<Orders>
<Order>
<OrderLine>
</OrderLine>
</Order>
</Orders>
</Customer>
</Customers>
Before saving the above XML data into the actual tables, I need to process it first. For this reason, I created corresponding table types. Each of these table types have an extra column (guid as ROWGUID) so that if I'm processing new data (not yet assigned primary key) I generate a unique key. I use this column to keep the relational integrity between different table types.
What is the SQL syntax to convert the above nested XML to their corresponding tables, keeping in mind that child records must reference the generated parent guid?
Try it like this:
DECLARE #xml XML=
N'<Customers>
<Customer>
<Id>1</Id>
<AccountNumber>12345</AccountNumber>
<Addresses>
<Address>
<Id>100</Id>
<street>my street></street>
<city>London</city>
</Address>
<Address>
<Id>101</Id>
<street>my street></street>
<city>Berlin</city>
</Address>
</Addresses>
<BankDetails>
<BankDetail>
<Id>222</Id>
<Iban>DE8439834934939434333</Iban>
</BankDetail>
<BankDetail>
<Id>228</Id>
<Iban>UK1237921391239123213</Iban>
</BankDetail>
</BankDetails>
<Orders>
<Order>
<OrderLine />
</Order>
</Orders>
</Customer>
</Customers>';
--This query will create a table #tmpInsert with all the data
SELECT cust.value('Id[1]','int') AS CustomerID
,cust.value('AccountNumber[1]','int') AS CustomerAccountNumber
,addr.value('Id[1]','int') AS AddressId
,addr.value('street[1]','nvarchar(max)') AS AddressStreet
,addr.value('city[1]','nvarchar(max)') AS AddressCity
,bank.value('Id[1]','int') AS BankId
,bank.value('Iban[1]','nvarchar(max)') AS BankIban
,ord.value('OrderLine[1]','nvarchar(max)') AS OrderLine
INTO #tmpInsert
FROM #xml.nodes('/Customers/Customer') AS A(cust)
OUTER APPLY cust.nodes('Addresses/Address') AS B(addr)
OUTER APPLY cust.nodes('BankDetails/BankDetail') AS C(bank)
OUTER APPLY cust.nodes('Orders/Order') AS D(ord);
--Here you can check the content
SELECT * FROM #tmpInsert;
--Clean-Up
GO
DROP TABLE #tmpInsert
Once you've got all your data in the table, you can use simple DISTINCT, GROUP BY, if needed ROW_NUMBER() OVER(PARTITION BY ...) to select each set separately for the proper insert.

How to make child as parent in xml tree

I am working on message broker. But the query I am doing is very simple and can be answered by any DB guy also .
here is the query code to read xml and getting xml output
SET OutputRoot.XMLNSC.root.row[rowCnt].product_Info = THE (SELECT THE(SELECT C.*:Codes.*:Code AS TyrePatternCd FROM T.*:Classification[] AS C
WHERE C.(XMLNSC.Attribute)Type = 'BRAND') AS product
FROM itemMaster.*:ItemMasterHeader[] AS T );
This gives xml output like
<root name="Product">
<row>
<product_Info>
<product>
<TyrePatternCd>002</TyrePatternCd>
</product>
</row>
</root>
How can I make it like
<root name="Product">
<row>
<product_Info>
<TyrePatternCd>002</TyrePatternCd>
</row>
</root>
If I remove the AS product in query it makes column tag in tree.
How can I make child as parent?
Use SELECT ITEM to omit the 'product' element, and directly assign the result.
SET OutputRoot.XMLNSC.root.row[rowCnt].product_Info = THE (SELECT ITEM THE(SELECT C.*:Codes.*:Code AS TyrePatternCd FROM T.*:Classification[] AS C
WHERE C.(XMLNSC.Attribute)Type = 'BRAND')
FROM itemMaster.*:ItemMasterHeader[] AS T );

How do I set the xmlns attribute on the root element in the generated XML by using T-SQL's xml data type method: query?

I've created a simplified version of my problem:
DECLARE #X XML =
'<Root xmlns="TestNS" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Test>
<Id>1</Id>
<InnerCollection>
<InnerItem>
<Value>1</Value>
</InnerItem>
<InnerItem>
<Value>2</Value>
</InnerItem>
<InnerItem>
<Value>3</Value>
</InnerItem>
</InnerCollection>
</Test>
<Test>
<Id>2</Id>
<InnerCollection>
<InnerItem>
<Value>5</Value>
</InnerItem>
<InnerItem>
<Value>6</Value>
</InnerItem>
<InnerItem>
<Value>7</Value>
</InnerItem>
</InnerCollection>
</Test>
</Root>'
I'm trying to write a query that takes each <Test> element and breaks it into a row. On each row I want to select the Id and the InnerCollection as XML. I want to create this InnerCollection XML for the first row (Id:1):
<InnerCollection xmlns="Reed.Api" xmlnsi="http//www.w3.org/2001/XMLSchema-instance">
<InnerItem>
<Value>1</Value>
</InnerItem>
<InnerItem>
<Value>2</Value>
</InnerItem>
<InnerItem>
<Value>3</Value>
</InnerItem>
</InnerCollection>
I tried doing that with this query but it puts a namespace I don't want on the elements:
;WITH XMLNAMESPACES
(
DEFAULT 'TestNS'
, 'http://www.w3.org/2001/XMLSchema-instance' AS i
)
SELECT
X.value('Id[1]', 'INT') Id
-- Creates a p1 namespace that I don't want.
, X.query('InnerCollection') InnerCollection
FROM #X.nodes('//Test') AS T(X)
My Google-fu isn't very strong today, but I imagine it doesn't make it any easier that the darn function is called query. I'm open to using other methods to create that XML value other than the query method.
I could use this method:
;WITH XMLNAMESPACES
(
DEFAULT 'TestNS'
, 'http://www.w3.org/2001/XMLSchema-instance' AS i
)
SELECT
X.value('Id[1]', 'INT') Id
,CAST(
(SELECT
InnerNodes.Node.value('Value[1]', 'INT') AS 'Value'
FROM X.nodes('./InnerCollection[1]//InnerItem') AS InnerNodes(Node)
FOR XML PATH('InnerItem'), ROOT('InnerCollection')
) AS XML) AS InnerCollection
FROM #X.nodes('//Test') AS T(X)
But that involves calling nodes on it to break it out into something selectable, and then selecting it back into XML using FOR XML... when it was XML to begin with. This seems like a inefficient method of doing this, so I'm hoping someone here will have a better idea.
This is how to do the SELECT using the query method to create the XML on each row that my question was looking for:
;WITH XMLNAMESPACES
(
'http://www.w3.org/2001/XMLSchema-instance' AS i
, DEFAULT 'TestNS'
)
SELECT
Test.Row.value('Id[1]', 'INT') Id
, Test.Row.query('<InnerCollection xmlns="TestNS" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">{InnerCollection}</InnerCollection>')
FROM #X.nodes('/Root/Test') AS Test(Row)