Your own code looks, as if you want to get multiple repeated elements out of an XML. Therefore you are using a WHILE loop in order to read the first, the second, the nth value one after the other. Correct so far?
Assumably you are looking for this actually:
--create a mockup-table with two rows
DECLARE @tbl TABLE(ID INT IDENTITY, SomeXml XML);
INSERT INTO @tbl VALUES
('<a>
<b>b11</b>
<b>b12</b>
<b>b13</b>
<b>b14</b>
<b>b15</b>
</a>>')
,('<a>
<b>b21</b>
<b>b22</b>
<b>b23</b>
</a>');
--The query
SELECT b.value('text()[1]','varchar(10)')
FROM @tbl t
CROSS APPLY t.SomeXml.nodes('/a/b') A(b);
The .nodes()
method will return each <b>
in a single row as derived table (the table's name is A
, the column's name is b
. You can set both names as you like it. The returned column is of type XML and it is a relative fragment. The .value()
method is used on this column to retrieve the content.
Some hints how to avoid loops
Anyway you should avoid loops whenever you can avoid them. Your own approach could be solved in a set-based manner with a tally-on-the-fly:
DECLARE @count INT=5;
WITH Tally(Nmbr) AS (SELECT TOP(@count) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values)
SELECT t.ID
,Nmbr
,t.SomeXml.value('(/a/b[sql:column("Nmbr")])[1]','varchar(10)') AS Numbered_B
FROM @tbl t
CROSS JOIN Tally;
This will use a list of 1 to 5 in order to read the XML elements by their position using sql:column()
. The second row will return two NULL-values, as there is no forth or fifth element.
We could even use a dynamically created numbers list
But you can even create the tally-on-the-fly depending on the actual count of <b>
elements like here:
SELECT t.ID
,Nmbr
,t.SomeXml.value('(/a/b[sql:column("Nmbr")])[1]','varchar(10)') AS Numbered_B
FROM @tbl t
CROSS APPLY (SELECT TOP(t.SomeXml.value('count(/a/b)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) A(Nmbr);
This works like the query before, but will use a list of 1 to n, where n is found dynamically.