Reputation: 5493
Given an XML variable with these contents:
<Root>
<Parent>
<ItemID>28</ItemID>
<Child>
<ItemID>28</ItemID>
</Child>
<Child>
<ItemID>28</ItemID>
</Child>
</Parent>
<Parent>
<ItemID>38</ItemID>
<Child>
<ItemID>38</ItemID>
</Child>
</Parent>
</Root>
I need to replace each /Root/Parent/ItemID value with a descending number starting from -1, and then replace each Child/ItemID with the value from it's parent ItemID element. For example, the XML above should be transformed into this:
<Root>
<Parent>
<ItemID>-1</ItemID>
<Child>
<ItemID>-1</ItemID>
</Child>
<Child>
<ItemID>-1</ItemID>
</Child>
</Parent>
<Parent>
<ItemID>-2</ItemID>
<Child>
<ItemID>-2</ItemID>
</Child>
</Parent>
</Root>
Easy to do in c#, I'm stumped on getting the syntax right in SQL Server. Unfortunately I have to do it this way to work with a legacy system.
Upvotes: 3
Views: 192
Reputation: 5493
Mikael's solution was elegant but in reality my XML was a bit more complicated than in the question (many more elements, some attributes, etc), so recreating the XML became more complicated and a bit of a maintenance issue. So in the end I solved the problem with some good old procedural code:
declare @XML xml = '<Root>
<Parent>
<ItemID>28</ItemID>
<Child>
<ItemID>28</ItemID>
</Child>
<Child>
<ItemID>28</ItemID>
</Child>
</Parent>
<Parent>
<ItemID>18</ItemID>
<Child>
<ItemID>18</ItemID>
</Child>
</Parent>
</Root>'
DECLARE @CountOfParent INT = @XML.value('count(/Root/Parent)', 'int')
WHILE @CountOfParent > 0
BEGIN
PRINT @CountOfParent
--Replace the current Parent/ItemID element with -@CountOfParent
SET @XML.modify('replace value of (/Root/Parent/ItemID/text())[sql:variable("@CountOfParent")][1] with -sql:variable("@CountOfParent")')
--Now loop through all of THIS elements <Child> elements, also setting the ItemID to -@CountOfParent
DECLARE @CountOfChildren INT = @XML.value('count(/Root/Parent[sql:variable("@CountOfParent")]/Child)', 'int')
WHILE @CountOfChildren > 0
BEGIN
set @XML.modify('replace value of (/Root/Parent[sql:variable("@CountOfParent")]/Child[sql:variable("@CountOfChildren")]/ItemID/text())[1] with -sql:variable("@CountOfParent")')
set @CountOfChildren = @CountOfChildren - 1
END
SET @CountOfParent = @CountOfParent - 1
END
SELECT @XML
However, as Mikael answered the question, I have accepted his answer and am just including this for reference.
Upvotes: 1
Reputation: 138960
Your XML is not valid so I changed it to what I think you meant.
You can shred the XML, use row_number() to calculate the new ItemID and rebuild it using for xml path
.
declare @XML xml = '
<Root>
<Parent>
<ItemID>28</ItemID>
<Child>
<ItemID>28</ItemID>
</Child>
<Child>
<ItemID>28</ItemID>
</Child>
</Parent>
<Parent>
<ItemID>38</ItemID>
<Child>
<ItemID>38</ItemID>
</Child>
</Parent>
</Root>'
select P.ItemID,
(
select P.ItemID
from P.Child.nodes('Child') as C(N)
for xml path('Child'), type
)
from
(
select -row_number() over(order by P.N) as ItemID,
P.N.query('Child') as Child
from @XML.nodes('/Root/Parent') as P(N)
) as P
for xml path('Parent'), root('Root'), type
Upvotes: 3