If you have to keep this with XML it should be much faster to shred the whole XML into a derivedTable and re-build it from scratch.
Try this:
CREATE TABLE #xmltable(
Id INT Identity (1,1) PRIMARY KEY CLUSTERED,
DataValue XML
);
CREATE PRIMARY XML INDEX indexratesheet ON #xmltable
(
DataValue
);
--Your test XML
INSERT INTO #xmltable (DataValue ) VALUES(N'<root xmlns:json="http://james.newtonking.com/projects/json">
<row json:Array="true" RowNumber="1">
<Column json:Array="true" Name="Number" Value="1" />
<Column json:Array="true" Name="HourFrom" Value="13.2" />
<Column json:Array="true" Name="HourTo" Value="13.3" />
<Column json:Array="true" Name="Rate" Value="0.895" />
</row>
<row json:Array="true" RowNumber="2">
<Column json:Array="true" Name="Number" Value="1" />
<Column json:Array="true" Name="HourFrom" Value="13.3" />
<Column json:Array="true" Name="HourTo" Value="13.4" />
<Column json:Array="true" Name="Rate" Value="0.907" />
</row>
</root>');
--The query to shred it
SELECT r.value(N'@RowNumber','int') AS RowNumber
,r.value(N'(Column[@Name="Number"]/@Value)[1]','int') AS Number
,r.value(N'(Column[@Name="HourFrom"]/@Value)[1]','decimal(10,4)') AS HourFrom
,r.value(N'(Column[@Name="HourTo"]/@Value)[1]','decimal(10,4)') AS HourTo
,r.value(N'(Column[@Name="Rate"]/@Value)[1]','decimal(10,4)') AS Rate
INTO #derivedTable
FROM #xmltable AS t
CROSS APPLY t.DataValue.nodes(N'/root/row') AS A(r);
--The query to re-build it
WITH XMLNAMESPACES('http://james.newtonking.com/projects/json' AS json)
SELECT 'true' AS [@json:Array]
,t.RowNumber AS [@RowNumber]
,'true' AS [Column/@json:Array]
,'Number' AS [Column/@Name]
,t.Number AS [Column/@Value]
,''
,'true' AS [Column/@json:Array]
,'HourFrom' AS [Column/@Name]
,t.HourFrom AS [Column/@Value]
,''
,'true' AS [Column/@json:Array]
,'HourTo' AS [Column/@Name]
,t.HourTo AS [Column/@Value]
,''
,'true' AS [Column/@json:Array]
,'Rate' AS [Column/@Name]
,t.Rate AS [Column/@Value]
,''
,'ValidationComments' AS [Column/@Name]
,'SomeValue' AS [Column/@Value]
FROM #derivedTable AS t
FOR XML PATH('row'),ROOT('root');
--Clean up (carefull with real data!)
GO
DROP TABLE #derivedTable;
DROP TABLE #xmltable
This is the result
<root xmlns:json="http://james.newtonking.com/projects/json">
<row json:Array="true" RowNumber="1">
<Column json:Array="true" Name="Number" Value="1" />
<Column json:Array="true" Name="HourFrom" Value="13.2000" />
<Column json:Array="true" Name="HourTo" Value="13.3000" />
<Column json:Array="true" Name="Rate" Value="0.8950" />
<Column Name="ValidationComments" Value="SomeValue" />
</row>
<row json:Array="true" RowNumber="2">
<Column json:Array="true" Name="Number" Value="1" />
<Column json:Array="true" Name="HourFrom" Value="13.3000" />
<Column json:Array="true" Name="HourTo" Value="13.4000" />
<Column json:Array="true" Name="Rate" Value="0.9070" />
<Column Name="ValidationComments" Value="SomeValue" />
</row>
</root>
UPDATE
Try this query, it will work for all different column lists, but it will repeat the namespace declaration. This is not wrong, but very annoying. At the moment I do not have the time to think about a hack. Let me know, if this works for you.
WITH XMLNAMESPACES('http://james.newtonking.com/projects/json' AS json)
,CTE AS
(
SELECT r.value(N'@RowNumber','int') AS RowNumber
,r.query('./*') AS TheContent
FROM #xmltable AS t
CROSS APPLY t.DataValue.nodes(N'/root/row') AS A(r)
)
SELECT CTE.TheContent AS [*]
,'ValidationComments' AS [Column/@Name]
,'SomeValue' AS [Column/@Value]
FROM CTE
FOR XML PATH('row'),ROOT('root')