Transform XML to Table data using XQuery in SQL Server - sql

Is it possible to achieve the following table from of output using XQuery in SQL Server
Edit: A change in my requirement, Consider i have a table which stores the below xml and also UserID
DECLARE #XML xml
set #XML = '<Security>
<FiscalYear Name="2012">
<Country Id="204">
<State Id="1">
<City Id="10"></City>
</State>
<State Id="2">
<City Id="20"></City>
<City Id="30"></City>
</State>
<State Id ="3"></State>
</Country >
</FiscalYear>
</Security>'
CREATE TABLE #tmp_user
(UserID INT,SecurityXML XML)
INSERT INTO #tmp_user
( UserID, SecurityXML )
VALUES ( 1,
#XML
)
Now how can i get a o/p like
Output:
UserID StateID CityID
1 1 10
1 2 20
1 2 30
1 3 0
Is it possible to achieve?

I modified your XML a bit because it was invalid. Change the end tag </Subsidiary> to </Country>.
declare #XML xml
set #XML =
'<Security>
<FiscalYear Name="2012">
<Country Id="204">
<State Id="1">
<City Id="10"></City>
</State>
<State Id="2">
<City Id="20"></City>
<City Id="30"></City>
</State>
<State Id ="3"></State>
</Country>
</FiscalYear>
</Security>'
select S.N.value('#Id', 'int') as StateID,
coalesce(C.N.value('#Id', 'int'), 0) as CityID
from #XML.nodes('/Security/FiscalYear/Country/State') as S(N)
outer apply S.N.nodes('City') as C(N)
A version using a table instead of XML variable
select T.UserID,
S.N.value('#Id', 'int') as StateID,
coalesce(C.N.value('#Id', 'int'), 0) as CityID
from #tmp_user as T
cross apply T.SecurityXML.nodes('/Security/FiscalYear/Country/State') as S(N)
outer apply S.N.nodes('City') as C(N)

Related

Parsing a xml string using sql

i am looking to parse the XML string using SQL .I like to have the data in separate columns.could some one please help?.
The string:
<item id="1" value="0"></item><item id="2" value="14"></item><item id="0" value="0"></item>
This is how you can do it in SQL Server (e.g., v2008):
create table #temp (xml_data xml)
insert into #temp values ('<item id="1" value="0"></item><item id="2" value="14"></item><item id="0" value="0"></item>')
select C.value('#id', 'int') as [id]
,C.value('#value', 'int') as [value]
from #temp cross apply
#temp.xml_data.nodes('item') as X(C)
drop table #temp
Which returns:
id value
----------- -----------
1 0
2 14
0 0

XML Without Record Element need to convert into Table using TSQL / SQL Server

XML without record element need to convert to a table using T-SQL in SQL Server.
DECLARE #X XML = '<ROOT>
<RECORD>
<ID>1</ID>
<AppCode>Code 1</AppCode>
<ID>2</ID>
<AppCode>Code 2</AppCode>
<ID>3</ID>
<AppCode>Code 3</AppCode>
<ID>4</ID>
<AppCode>Code 4</AppCode>
<ID>5</ID>
<AppCode>Code 5</AppCode>
</RECORD>
</ROOT>'
I tried like this
SELECT
CASE WHEN items.item.value('local-name(.)', 'varchar(300)') = 'ID'
THEN items.item.value('text()[1]', 'varchar(300)')
END AS ID,
CASE WHEN items.item.value('local-name(.)', 'varchar(300)') = 'AppCode'
THEN items.item.value('text()[1]', 'varchar(300)')
END AS AppCode
FROM
#X.nodes('/ROOT/record/*') AS items(item)
Result was not correct -
ID AppCode
-----------------
1 NULL
NULL Code 1
2 NULL
NULL Code 2
I need a result like this::
ID Appcode
---------------
1 Code 1
2 Code 2
3 Code 3
4 Code 4
5 Code 5
UPDATE 2: This will be the best approach
Try it out
WITH Numbers AS
(SELECT TOP(#X.value('count(//ID)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nmbr FROM master..spt_values)
SELECT Nmbr
,#X.value('(//ID[sql:column("Nmbr")])[1]','int') AS ID
,#X.value('(//AppCode[sql:column("Nmbr")])[1]','nvarchar(max)') AS AppCode
,#X.value('(//SomeMore[sql:column("Nmbr")])[1]','nvarchar(max)') AS SomeMore
FROM Numbers;
The idea
count the IDs and return a derived tally table with a running number (in this case 1,2,3,4,5). Then use this number to address the elements by their position.
Two more approaches, just for fun :-)
This should work with many columns pretty fast:
DECLARE #X XML = '<ROOT>
<RECORD>
<ID>1</ID>
<AppCode>Code 1</AppCode>
<ID>2</ID>
<AppCode>Code 2</AppCode>
<ID>3</ID>
<AppCode>Code 3</AppCode>
<ID>4</ID>
<AppCode>Code 4</AppCode>
<ID>5</ID>
<AppCode>Code 5</AppCode>
</RECORD>
</ROOT>';
WITH allIDs AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Pos
,id.value('text()[1]','int') AS ID
FROM #X.nodes('/ROOT/RECORD/ID') A(id)
)
SELECT ai.Pos
,ai.ID
,#X.value('/ROOT[1]/RECORD[1]/AppCode[sql:column("ai.Pos")][1]','nvarchar(max)') AS AppCode
,#X.value('/ROOT[1]/RECORD[1]/SomeMore[sql:column("ai.Pos")][1]','nvarchar(max)') AS SomeMore
FROM allIDs ai
ORDER BY ai.Pos;
The idea:
Read the IDs and find their position
Read the corresponding elements by using the ID's position via sql:column()
UPDATE: One more approach using conditional aggregation
This should be faster:
WITH AllNodes AS
(
SELECT (ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1) / 3 AS RowGroup
,nd.value('local-name(.)','nvarchar(max)') AS NodeName
,nd.value('text()[1]','nvarchar(max)') AS NodeValue
FROM #X.nodes('/ROOT/RECORD/*') A(nd)
)
SELECT RowGroup
,MAX(CASE WHEN NodeName='ID' THEN NodeValue END) AS ID
,MAX(CASE WHEN NodeName='AppCode' THEN NodeValue END) AS AppCode
,MAX(CASE WHEN NodeName='SomeMore' THEN NodeValue END) AS SomeMore
FROm AllNodes
GROUP BY RowGroup
The idea
We read all nodes as an EAV-structure. The (ROW_NUMBER-1)/3 (integer division!) will return a group index we can use in GROUP BY. The rest is old-fashioned PIVOT.
Warnings for both approaches:
This needs a structure without missing elements. If XML might include sections, where one of the follower nodes is missing, this won't work.
The solution with ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) did work for me in all cases I've used it. But it is worth to mention, that this is not guaranteed to return the correct position.
Assuming this is the format you are after:
ID Appcode
---------------
1 Code 1
2 Code 2
This is how I would do it:
DECLARE #X XML = '
<ROOT>
<RECORD>
<ID>1</ID>
<AppCode>Code 1</AppCode>
<ID>2</ID>
<AppCode>Code 2</AppCode>
</RECORD>
</ROOT>';
SELECT V.ID,AppCode
FROM #X.nodes('/ROOT/RECORD') AS X(R)
CROSS APPLY (VALUES(X.R.value('(./ID/text())[1]','int'),X.R.value('(./AppCode/text())[1]','varchar(10)')),
(X.R.value('(./ID/text())[2]','int'),X.R.value('(./AppCode/text())[2]','varchar(10)'))) V(ID,AppCode)
This, however, won't scale. So if you have an ID of 3, 4, it's not going to give you more rows.
If you can modify your XML format and use query as following to get result
DECLARE #X XML = '<ROOT>
<RECORD>
<ID>1</ID>
<AppCode>Code 1</AppCode>
</RECORD>
<RECORD>
<ID>2</ID>
<AppCode>Code 2</AppCode>
</RECORD>
<RECORD>
<ID>3</ID>
<AppCode>Code 3</AppCode>
</RECORD>
<RECORD>
<ID>4</ID>
<AppCode>Code 4</AppCode>
</RECORD>
<RECORD>
<ID>5</ID>
<AppCode>Code 5</AppCode>
</RECORD>
</ROOT>'
DECLARE #doc int
EXEC sp_xml_preparedocument #doc OUTPUT, #X
SELECT ID, AppCode
FROM OPENXML(#doc,'/ROOT/RECORD',2)
WITH (
ID int,
AppCode varchar(10)
)
The output will like
ID AppCode
1 Code 1
2 Code 2
3 Code 3
4 Code 4
5 Code 5

how to get the count of the xml nodes

I have the following xml nodes
<PPAProposal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Answers>
<AnswersList>
<Entry key="ac5_a_InsuredDetailsUpload" type="System.Collections.ArrayList">
<value>
<anyType xsi:type="xsd:string">C___documents__PPA_813FN0011684_190219_1003.pdf</anyType>
<anyType xsi:type="xsd:string">New Doc 2019-02-18 13.37.43 (1).pdf</anyType>
</value>
</Entry>
</Answers>
</AnswersList>
</PPAProposal>
I need to fetch the count of the "AnyType" nodes.
I have written query something like below.
SELECT
(CASE WHEN Data.value('(/*/Answers/AnswersList/Entry[#key="ac5_a_InsuredDetailsUpload"]/value)[1]', 'nvarchar(max)')!=''
THEN
(SELECT Count((ISNULL(Col.value('(value)[1]', 'nvarchar(max)'), '0'))) AS upload
FROM Data.nodes('(/*/Answers/AnswersList/Entry[#key="ac5_a_InsuredDetailsUpload"])') AS Tbl(Col))
ELSE 0
END
)
FROM TEMPTABLE
I'm able to return count 1 instead of count 2 as there is 2 nodes in the "Value" node.
You can count anyType elements directly in the XPath :
SELECT
t.Data.value(
'count(/*/Answers/AnswersList/Entry[#key="ac5_a_InsuredDetailsUpload"]/value/anyType)',
'int'
) as anytype_count
FROM TEMPTABLE t;
dbfiddle demo
declare #XMLDATA xml;
set #XMLDATA = '
<PPAProposal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AnswersList>
<Answers>
<Entry key="ac5_a_InsuredDetailsUpload" type="System.Collections.ArrayList">
<value>
<anyTypexsi:type="xsd:string">C___documents__PPA_813FN0011684_190219_1003.pdf</anyType>
<anyType xsi:type="xsd:string">New Doc 2019-02-18 13.37.43 (1).pdf</anyType>
</value></Entry></Answers></AnswersList></PPAProposal>'
DECLARE #XMLDocPointer INT;
EXEC sp_xml_preparedocument #XMLDocPointer OUTPUT,#XMLDATA;
SELECT value INTO #TEMPDATA
FROM OPENXML( #XMLDocPointer, '/PPAProposal/AnswersList/Answers/Entry/value/anyType', 2 )
WITH ( value NVARCHAR(1000), anyType NVARCHAR(1000) )
EXEC sp_xml_removedocument #XMLDocPointer;
select count(*)total from #TEMPDATA
drop table #TEMPDATA

Use SQL Server modify('insert') to append data to xml column

Consider the following situation. I have the following table
CREATE TABLE [dbo].[GoldenEgg]
(
rowIndex int NOT NULL IDENTITY(1,1),
AccountNumber varchar(256) NULL,
SubscriptionID int NOT NULL,
SubscriptionData_XML xml NULL,
SubscriptionData_AFTER_XML NULL
CONSTRAINT [PK_GoldenEgg]
PRIMARY KEY CLUSTERED ([rowIndex] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GoldenEgg sample data:
SubscriptionData_XML data for SubscriptionID 6070:
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
</Value>
</Item>
</NVPList>
I want to append all account numbers for each SubscriptionID to the already existing xml <Value> node in the SubscriptionData_XML column and I do not want to add account numbers that already exist in the xml.
So for SubscriptionID 6070 account number 39448474 should only be listed once in the xml like so:
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem>56936495</ValueItem>
<ValueItem>70660044</ValueItem>
<ValueItem>41447395</ValueItem>
</Value>
</Item>
</NVPList>
If there are not other nodes within your XML you might choose the FLWOR-query.
Some hints:
first I create a mock-up table and fill it with data
I use and updateable CTE to collect the data
I use a FOR XML-sub-select without a namespace to build the <Value> node wihtout bothering about already existing IDs in your actual XML
I use a FLWOR-query() to build up the full XML out of the just created Value-node
As this CTE is updateable, I can use it directly for the UPDATE
The final SELECT * FROM #tbl shows to you, that all AFTER_XML are filled
Try this:
DECLARE #tbl TABLE(rowIndex INT IDENTITY,AccountNumber INT,SubscriptionID INT, SubscriptionData_XML XML,SubscriptionData_AFTER_XML XML);
INSERT INTO #tbl VALUES
(1111,6070,N'<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
</Value>
</Item>
</NVPList>',NULL)
,(2222,6070,NULL,NULL)
,(3333,6070,NULL,NULL)
,(4444,6070,NULL,NULL)
,(5555,6071,N'<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
</Value>
</Item>
</NVPList>',NULL)
,(6666,6071,NULL,NULL)
,(7777,6071,NULL,NULL)
,(8888,6071,NULL,NULL);
--Here starts the updateable CTE
WITH UpdateableCTE AS
(
SELECT t1.rowIndex
,t1.SubscriptionData_AFTER_XML
,(
SELECT t2.AccountNumber AS ValueItem
FROM #tbl AS t2
WHERE t2.SubscriptionID=t1.SubscriptionID
FOR XML PATH(''),ROOT('Value'),TYPE
).query
(N'declare default element namespace "http://www.whatevernamspace.com/v1";
let $nd:=/*:Value
return
<NVPList>
<Item>
<Name>{sql:column("XmlName")}</Name>
<Value>
{
for $vi in $nd/*:ValueItem
return <ValueItem>{$vi/text()}</ValueItem>
}
</Value>
</Item>
</NVPList>
'
) AS NewXML
FROM #tbl AS t1
CROSS APPLY( SELECT t1.SubscriptionData_XML.value('(//*:Name)[1]','nvarchar(max)') AS XmlName) AS x
WHERE SubscriptionData_XML IS NOT NULL
)
--The UPDATE statement
UPDATE UpdateableCTE SET SubscriptionData_AFTER_XML=NewXML
FROM UpdateableCTE;
--The SELECT to check the success
SELECT * FROM #tbl
I was able to accomplish this task with a sql UPDATE statement using the xml modify() method and without using any loops. Here is a breakdown of the solution:
1) I had to get all the AccountNumbers for the SubscriptionID and format them in
into xml <ValueItem> nodes.
SQL QUERY 1:
SELECT
ge.SubscriptionID,
CAST((SELECT DISTINCT ValueItem = ISNULL(ge2.AccountNumber,'')
FROM dbo.GoldenEgg ge2
WHERE ge2.SubscriptionID = ge.SubscriptionID
FOR XML PATH('')) AS xml) AS AccountNumberXml
FROM dbo.GoldenEgg ge
WHERE ge.SubscriptionData_XML IS NOT NULL
SQL QUERY 1 RESULT:
SQL QUERY 1 XML RESULT (SubscriptionID 6070):
<ValueItem>39448474</ValueItem>
<ValueItem>41447395</ValueItem>
<ValueItem>56936495</ValueItem>
<ValueItem>70660044</ValueItem>
2) Now that I have the AccountNumbers in a single value, I can now use the xml modify() method and insert the AccountNumberXml value into the last position of the <Value> xml node. I will do this using an UPDATE statement with INNER JOIN. Also note that I initally set SubscriptionData_AFTER_XML equal to SubscriptionData_XML before doing anything.
SQL QUERY 2:
UPDATE ge
SET SubscriptionData_AFTER_XML.modify
('declare default element namespace "http://www.whatevernamspace.com/v1";
insert sql:column("t1.AccountNumberXml") as last into (/NVPList/Item/Value)[1]')
FROM dbo.GoldenEgg ge
INNER JOIN (SELECT
ge2.SubscriptionID,
CAST((SELECT DISTINCT ValueItem = ISNULL(ge1.AccountNumber,'')
FROM dbo.GoldenEgg ge1
WHERE ge1.SubscriptionID = ge2.SubscriptionID
FOR XML PATH('')) AS xml) as AccountNumberXml
FROM dbo.GoldenEgg ge2
WHERE ge2.SubscriptionData_AFTER_XML IS NOT NULL) t1 ON t1.SubscriptionID = ge.SubscriptionID
WHERE ge.SubscriptionData_AFTER_XML IS NOT NULL
SQL QUERY 2 RESULT:
SQL QUERY 2 XML RESULT (SubscriptionID 6070 SubscriptionData_AFTER_XML column):
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem xmlns="">39448474</ValueItem>
<ValueItem xmlns="">41447395</ValueItem>
<ValueItem xmlns="">56936495</ValueItem>
<ValueItem xmlns="">70660044</ValueItem>
</Value>
</Item>
</NVPList>
As you may see there are now two problems with the final xml result in the SubscriptionData_AFTER_XML column.
Problem 1
For subscriptionID 6070 AccountNumber 39448474 is being repeated in the <ValueItem> node list, which I do not want. To fix this I have to query the current AccountNumber values in the xml and exclude those AccountNumbers from the previous INNER JOIN
SQL QUERY 3:
This query will give me a result set with all the current AccountNumbers in the SubscriptionData_XML column, which I can then use to exclude these AccountNumbers from the SQL QUERY 1 result set
SELECT SubscriptionID, t.c.value('.', 'varchar(MAX)') as CurrentValueItems
FROM dbo.GoldenEgg
CROSS APPLY SubscriptionData_XML.nodes('declare default element namespace "http://www.whatevernamspace.com/v1";
/NVPList/Item/Value/ValueItem') as t(c)
WHERE SubscriptionData_XML IS NOT NULL
SQL QUERY 3 RESULT:
Now putting it all together to get the correct final result
SQL QUERY 4:
UPDATE ge
SET SubscriptionData_AFTER_XML.modify
('declare default element namespace "http://www.whatevernamspace.com/v1";
insert sql:column("t1.AccountNumberXml") as last into (/NVPList/Item/Value)[1]')
FROM dbo.GoldenEgg ge
INNER JOIN (SELECT
ge2.SubscriptionID,
CAST((SELECT DISTINCT ValueItem = ISNULL(ge1.AccountNumber,'')
FROM dbo.GoldenEgg ge1
--make sure we are not inserting AccountNumbers that already exists in the subscription data
WHERE ge1.AccountNumber NOT IN (SELECT t.c.value('.', 'varchar(MAX)') as CurrentValueItems
FROM dbo.GoldenEgg
CROSS APPLY SubscriptionData_XML.nodes('declare default element namespace "http://www.whatevernamspace.com/v1";
/NVPList/Item/Value/ValueItem') as t(c)
WHERE SubscriptionData_XML IS NOT NULL
AND SubscriptionID = ge2.SubscriptionID)
AND ge1.SubscriptionID = ge2.SubscriptionID
FOR XML PATH('')) AS xml) as AccountNumberXml
FROM dbo.GoldenEgg ge2
WHERE ge2.SubscriptionData_AFTER_XML IS NOT NULL) t1 ON t1.SubscriptionID = ge.SubscriptionID
WHERE ge.SubscriptionData_AFTER_XML IS NOT NULL
SQL QUERY 4 XML RESULT (SubscriptionID 6070 SubscriptionData_AFTER_XML column):
As you can see AccountNumber 39448474 is now only listed once in the xml
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem xmlns="">41447395</ValueItem>
<ValueItem xmlns="">56936495</ValueItem>
<ValueItem xmlns="">70660044</ValueItem>
</Value>
</Item>
</NVPList>
Problem 2
When the with AccountNumber node list is inserted, it is being inserted with an empty xmlns="" namespace. This is query I used to remove the empty xmlns="" namespace.
SQL QUERY 5:
UPDATE dbo.GoldenEgg
SET SubscriptionData_AFTER_XML = CONVERT(XML, REPLACE(CONVERT(NVARCHAR(MAX), SubscriptionData_AFTER_XML), N'xmlns=""',''))
WHERE SubscriptionData_AFTER_XML IS NOT NULL
SQL QUERY 5 XML RESULT (SubscriptionID 6070):
<NVPList xmlns="http://www.whatevernamspace.com/v1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Item>
<Name>AccountNumbers</Name>
<Value>
<ValueItem>39448474</ValueItem>
<ValueItem>41447395</ValueItem>
<ValueItem>56936495</ValueItem>
<ValueItem>70660044</ValueItem>
</Value>
</Item>
</NVPList>
I hope this helps anyone who may need to do something similar

How to using for loop using XQuery to delete xml nodes [duplicate]

This question already has answers here:
Deleting Multiple Nodes in Single XQuery for SQL Server
(2 answers)
Closed 8 years ago.
I have an XML parameter #Xml in SQL Stored procedure like below:
<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
<Data>
<Id>2</Id>
<Name>Alex</Name>
<Des>Des2</Des>
</Data>
<Data>
<Id>3</Id>
<Name>Amy</Name>
<Des>Des3</Des>
</Data>
</Root>
now, I want to delete several nodes in this xml, the filter is like this (Id = 2 AND Name = 'Alex') OR (Id = 3 AND Name = 'Amy'), I don't want to use Cursor or something like this, just using one single script to do it, I am try to my best for this, but I can't get the answer, anybody can help me ? Thanks in advance!!!
The output should be :
<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
</Root>
PS: the filter is a #table, so, the actually question is , how to remove the specific records in XML which contains in #table?
Id Name
2 Alex
3 Amy
Declare #Xml XML
set #Xml = '<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
<Data>
<Id>2</Id>
<Name>Alex</Name>
<Des>Des2</Des>
</Data>
<Data>
<Id>3</Id>
<Name>Amy</Name>
<Des>Des3</Des>
</Data>
</Root>'
create TABLE #tempResult
(
Id Int,
Name Varchar(10)
)
insert into #tempResult
values(2, 'Alex'), (3, 'Amy')
SET #Xml = (
SELECT Node.value('Id[1]','int') AS PId, Node.value('Name[1]','Varchar(10)') AS PName
FROM #Xml.nodes('//Data') AS T(Node)
OUTER APPLY (
SELECT tr.Id, tr.Name
FROM #tempResult tr
WHERE Node.value('Id[1]','int') = tr.Id and Node.value('Name[1]','Varchar(10)') = tr.Name
) a
WHERE a.Id IS NULL
FOR XML PATH(''), TYPE
)
select #Xml
drop table #tempResult
I got results like this
1
Kevin
But I need the the whole xml contains Root node:
<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
</Root>
How can I reach this? Any help? I am an new leaner for SQL to XML, please help me !!!
I got the answer, thanks all you guys
declare #xml xml
set #xml = '<Root><Header>123</Header><Data><Id>1</Id><Name>Kevin</Name><Des>Des1</Des></Data><Data><Id>2</Id><Name>Alex</Name><Des>Des2</Des></Data><Data><Id>3</Id><Name>Amy</Name><Des>Des3</Des></Data><Tail>456</Tail></Root>'
--set #xml.modify('delete /Root/Data[(Id[.=1] and Name[.="Kevin"]) or (Id[.=2] and Name[.="Alex"])]')
select #xml
declare #parameter XML
set #parameter = (SELECT Node.value('(Id)[1]', 'Int') AS Id,
Node.value('(Name)[1]', 'Varchar(10)') AS Name
FROM #xml.nodes('Root/Data') TempXML(Node)
WHERE Node.value('(Id)[1]', 'Int') != 3
for xml path('Root'), TYPE)
--select #parameter
declare #sql nvarchar(max)
set #sql = convert(nvarchar(max),
#parameter.query('for $i in (/Root) return concat("(Id[.=",
string($i/Id[1]),
"] and Name[.=""",
string($i/Name[1]),
"""]) or")')
)
set #sql = '['+substring(#sql,0,len(#sql)-2)+']'
set #sql = 'set #xml.modify(''delete /Root/Data'+#sql+''')'
select #sql
exec sp_executesql #sql, N'#xml xml output', #xml output
select #xml
You can shred the xml on the nodes in the root node #xml.nodes('/Root/*') and rebuild it using the XML from the shredded nodes R.X.query('.').
In a where not exists clause you exclude the Data nodes that is present in the temp table.
select R.X.query('.')
from #xml.nodes('/Root/*') as R(X)
where not exists (
select *
from #tempResult as T
where T.Id = R.X.value('(Id/text())[1]', 'int') and
T.Name = R.X.value('(Name/text())[1]', 'varchar(100)') and
R.X.value('local-name(.)', 'varchar(100)') = 'Data'
)
for xml path(''), root('Root'), type
Result:
<Root>
<Header>123</Header>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
<Tail>456</Tail>
</Root>
SQL Fiddle