Thursday, March 4, 2010

Working with xml data type in MS SQL server

Previously I had worked with xml data type in SQL 2005. There I was serializing the object in to xml and putting it into xml variable in SQL table. And whenever needed I would pull the xml from SQL table and create an object (deserialize). But recently I was presented with a challenge to manipulate the xml in SQL (T-SQL) itself.

In this scenario, xml text was being stored in a table as nvarchar(max). Let’s say the column name is “xmlFrag”. A function was written to return rows based on a set parameter. But for the set parameter, if the result set returns multiple rows, the function would combine the xmlFrag column (from both rows) and return one xml fragment. Below is the example.

1st row of the resultset.




Gud Day Biscuits


Cashew nuts


Honey






2nd row of the resultset.




Britannia Marie


Wheat


Barley






Then the function would combine these 2 rows data into one single xml fragment. The combined xml fragment would look like…




Gud Day Biscuits


Cashew nuts


Honey




Britannia Marie


Wheat


Barley






If you notice the highlighted node, the order of the feature is repeating (1,2,1,2). This was creating a problem in processing the nodes. And hence I was asked to make the order unique (1,2,3,4).

We had 2 approaches, one to make changes in the c# code itself. Two, make changes in the SQL function. Regarding making changes in the c# code, it would have been a huge task because this feature was being used in many places. So I decided to go ahead with changing the SQL function itself. Below are the steps followed to achieve the same.

Step 1. As we already know the function will return merged xml fragment from 2 rows. In the first step we will have to take the merged xml fragment into an xml variable and count the number of nodes in the xml fragment. So the total nodes are 4 in our case.

Step 2. After we know the total number of nodes where we have to make changes, we will have to loop through the nodes and modify the same. In the second step, we will have to use the .modify method on the xml variable. In this scenario, we actually have 2 variants. One, which node we are pointing to. Two, what value we want to replace. In both situations, the @counter variable will be used.

So in the modify method, the first part is “replace value of”. In here we will have to point to the correct node. This can be achieved by pointing to the node in the hierarchy (/Product/Item/Features/Feature) and then pointing to the attribute of the node (/@Order). Here since there are 4 nodes, we will have to point to the single node by specifying the position. This can be achieved by [position()=sql:variable(“@counter”)].

The second part (replacing with) we will have to specify the counter variable. This can be achieved by sql:variable(“@counter”). And of course we will have to increment the @counter variable.


Below is the complete solution.


DECLARE @processed_xml as xml
DECLARE @counter as INT
DECLARE @node_count as INT
-- This is the previous return statement in function
-- RETURN '' + replace(replace(@new_text_all,'',''),'','') + ''

-- New code to add sequence number to order attribute of Feature node.
set @counter = 1;
set @processed_xml = '' + replace(replace(@new_text_all,'',''),'','') + ''

-- Count of Feature Nodes in xml
SELECT @node_count = @processed_xml.query(' {count(/Product/Item/Features/Feature)}').value('count[1]','int')

WHILE @counter <= @node_count
BEGIN
SET @processed_xml.modify('replace value of ((/Product/Item/Features/Feature/@Order)[position()=sql:variable("@counter")][1])
with sql:variable("@counter")')
SET @counter = @counter + 1
END

-- New return statement for function
RETURN cast(@processed_xml as nvarchar(max))


Challenges faced were related to using SQL variable in both replace value of clause as well as with clause. The final output would look like below.




Gud Day Biscuits


Cashew nuts


Honey




Britannia Marie


Wheat


Barley






Feel free to contact me in case you need help.
-Vighnesh Bendre

No comments: