How to make child as parent in xml tree - sql

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

Related

How to put an attribute on the root element, and only the root element, in FOR XML PATH?

I'm generating XML from a SQL Server table.
This is my code:
;WITH XMLNAMESPACES
(
'http://www.w3.org/2001/XMLSchema-instance' AS xsi
--,DEFAULT 'http://www.w3.org/2001/XMLSchema-instance' -- xmlns
)
SELECT
'T_Contracts' AS "#tableName",
(SELECT * FROM T_Contracts
FOR XML PATH('row'), TYPE, ELEMENTS xsinil)
FOR XML PATH('table'), TYPE, ELEMENTS xsinil
I want the result to look like this (note: attribute tableName on the root element):
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row>
<VTR_UID>779FE899-4E81-4D8C-BF9B-3F17BC1DF146</VTR_UID>
<VTR_MDT_ID>0</VTR_MDT_ID>
<VTR_VTP_UID xsi:nil="true" />
<VTR_Nr>0050/132251</VTR_Nr>
</row>
</table>
But it duplicates the XSI namespace on the row element...
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<VTR_UID>779FE899-4E81-4D8C-BF9B-3F17BC1DF146</VTR_UID>
<VTR_MDT_ID>0</VTR_MDT_ID>
<VTR_VTP_UID xsi:nil="true" />
<VTR_Nr>0050/132251</VTR_Nr>
</row>
</table>
What's the correct way to add an attribute to the root element, and only the root element ?
Note
NULL-values must be returned as <columnName xsi:nil="true" /> and not be omitted.
(And no xml.modify after the select)
Please note that this is NOT a duplicate of an existing question.
This annoying behaviour of repeated namespaces with sub-queries was a reported issue for more than 10 years on MS-Connect with thousands of votes. This platform was dismissed, so was this issue and there is no perspective that MS will ever solve this.
Just to be fair: It is not wrong to repeat the namespace declaration. It's just bloating the string-based output...
Even stranger is the the unsupported attribute on a root level node...
Well, if you need a head-ache, you might look into OPTION EXPLICIT :-)
The accepted answer by Marc Guillot will not produce xsi:nil="true" attributes as you seem to need them. It will just wrap your result with the appropriate root node.
Finally: This cannot be solved with XML methods, you can try this:
Update: Found a way, see below...
DECLARE #tbl TABLE(ID INT,SomeValue INT);
INSERT INTO #tbl VALUES(1,1),(2,NULL);
SELECT CAST(REPLACE(CAST(
(
SELECT *
FROM #tbl
FOR XML PATH('row'),ROOT('table'),TYPE, ELEMENTS XSINIL
) AS nvarchar(MAX)),'<table ','<table tableName="T_Contracts" ') AS XML);
The result
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row>
<ID>1</ID>
<SomeValue>1</SomeValue>
</row>
<row>
<ID>2</ID>
<SomeValue xsi:nil="true" />
</row>
</table>
The idea in short:
We create the XML without a sub-query and add the attribute with a string method into the casted XML.
As the position of an attribute is not important, we can add it everywhere.
alternatively you might search for the first closing > and use STUFF() there...
UPDATE
Heureka, I just found a way, to create this without swithing to string, but it's clumsy :-)
DECLARE #tbl TABLE(ID INT,SomeValue INT);
INSERT INTO #tbl VALUES(1,1),(2,NULL);
SELECT
(
SELECT 'T_Contracts' AS [#tableName]
,(
SELECT 'SomeRowAttr' AS [#testAttr] --added this to test row-level attributes
,*
FROM #tbl
FOR XML PATH('row'),TYPE, ELEMENTS XSINIL
)
FOR XML PATH('table'),TYPE, ELEMENTS XSINIL
).query('<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">{/table/#*}
{
for $nd in /table/row
return
<row>{$nd/#*}
{
$nd/*
}
</row>
}
</table>');
The result
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row testAttr="SomeRowAttr">
<ID>1</ID>
<SomeValue>1</SomeValue>
</row>
<row testAttr="SomeRowAttr">
<ID>2</ID>
<SomeValue xsi:nil="true" />
</row>
</table>
Why don't you build manually the root element ?
Example:
with CTE as (
select (select * from T_Contracts for xml path('row')) as MyXML
)
select '<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">' +
MyXML +
'</table>'
from CTE
Unfortunately you cannot do this with the SQL Server out of the box nor exists an elegant way to do that. To alleviate the issue, you can replace NULLs with empty strings. This will remove xmlns, but you have to define your select list explicitly as follows. Moreover, this works only with character string data types as you cannot assign an empty string ('' in ISNULL function) to-for example-an integer.
;WITH XMLNAMESPACES
(
'http://www.w3.org/2001/XMLSchema-instance' AS xsi
--,DEFAULT 'http://www.w3.org/2001/XMLSchema-instance' -- xmlns
)
SELECT 'T_Contracts' AS "#tableName",
(
SELECT
ISNULL(VTR_UID, '') 'row/VTR_UID'
,ISNULL(VTR_MDT_ID, '') 'row/VTR_MDT_ID'
,ISNULL(VTR_VTP_UID, '') 'row/VTR_VTP_UID'
,ISNULL(VTR_Nr, '') 'row/VTR_Nr'
FROM T_Contracts
FOR XML PATH(''), TYPE
)
FOR XML PATH('table'), TYPE, ELEMENTS xsinil
The result will be like below:
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row>
<VTR_UID>779FE899-4E81-4D8C-BF9B-3F17BC1DF146</VTR_UID>
<VTR_MDT_ID>0</VTR_MDT_ID>
<VTR_VTP_UID />
<VTR_Nr>0050/132251</VTR_Nr>
</row>
</table>

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

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.

count occurrence of xml child nodes from two identical parent nodes

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

XML sql query , attribute as column

I need to query xml data available in server
<root>
<Parameter>
<Param>APP_REG_NUMBER</Param>
<Value>AL/T/2010/86</Value>
</Parameter>
<Parameter>
<Param>SUBLINEID</Param>
<Value>235931</Value>
</Parameter>
</root>
This is the structure I am saving data into SQL Server, I need output as follows:
Filed1 , Filed2 , APP_REG_NUMBER, SUBLINEID
something something , AL/T/2010/86, 235931
please do the needful
You can use XQuery for this, but realize you are recreating key-value-pairs in XML, which negates the ability to use schemas or XQuery/XPATH in any reasonable manner. Consider changing the format to:
<root>
<APP_REG_NUMBER>AL/T/2010/86</APP_REG_NUMBER>
<SUBLINEID>235931</SUBLINEID>
</root>
I digress... the query you want is:
DECLARE #testXml xml = N'<root>
<Parameter><Param>APP_REG_NUMBER</Param><Value>AL/T/2010/86</Value></Parameter>
<Parameter><Param>SUBLINEID</Param><Value>235931</Value></Parameter>
</root>'
SELECT
#testXml.value('(//Parameter[Param/text()="APP_REG_NUMBER"]/Value)[1]', 'nvarchar(50)') as APP_REG_NUMBER,
#testXml.value('(//Parameter[Param/text()="SUBLINEID"]/Value)[1]', 'nvarchar(50)') as SUBLINEID
You use the //Parameter syntax to find all Parameter elements and then filter them ([Param/text()="foobar"]) to only those which have a child named Value that have the inner text of SUBLINEID. From there, you navigate to the /Value child element and return the first result ((query)[1]).