I have two tables:
I want to get a list of the values for each NodePath for each RowId.
This query uses the sql:column
function in the xpath of the values method on the Settings column but it returns the NodePath itself instead of the value:
declare @settings table (RowId int identity, Settings xml)
insert @settings (Settings) values ('<settings><Settings1><Settings1a><Setting1a1>1-1a1</Setting1a1></Settings1a><Setting1b>1-1b</Setting1b><Setting1c>1-1c</Setting1c></Settings1><Settings2><Setting2a>1-2a</Setting2a></Settings2></settings>')
insert @settings (Settings) values ('<settings><Settings1><Settings1a><Setting1a1>2-1a1</Setting1a1></Settings1a><Setting1b>2-1b</Setting1b><Setting1c>2-1c</Setting1c></Settings1><Settings2><Setting2a>2-2a</Setting2a></Settings2></settings>')
insert @settings (Settings) values ('<settings><Settings1><Settings1a><Setting1a1>3-1a1</Setting1a1></Settings1a><Setting1b>3-1b</Setting1b><Setting1c>3-1c</Setting1c></Settings1><Settings2><Setting2a>3-2a</Setting2a></Settings2></settings>')
declare @nodesToFind table (NodePath varchar(max))
insert @nodesToFind (NodePath) values ('/Settings/Settings1/Settings1a/Setting1a1')
insert @nodesToFind (NodePath) values ('/Settings/Settings1/Setting1b')
insert @nodesToFind (NodePath) values ('/Settings/Settings1/Setting1c')
insert @nodesToFind (NodePath) values ('/Settings/Settings2/Setting2a')
select
S.RowId,
NTF.NodePath,
S.Settings.value('(sql:column("NodePath"))[1]', 'varchar(max)')
from @settings S
cross apply @nodesToFind NTF
The result is this:
RowId NodePath Value
----- ----------------------------------------- -----------------------------------------
1 /Settings/Settings1/Settings1a/Setting1a1 /Settings/Settings1/Settings1a/Setting1a1
2 /Settings/Settings1/Settings1a/Setting1a1 /Settings/Settings1/Settings1a/Setting1a1
3 /Settings/Settings1/Settings1a/Setting1a1 /Settings/Settings1/Settings1a/Setting1a1
1 /Settings/Settings1/Setting1b /Settings/Settings1/Setting1b
2 /Settings/Settings1/Setting1b /Settings/Settings1/Setting1b
3 /Settings/Settings1/Setting1b /Settings/Settings1/Setting1b
1 /Settings/Settings1/Setting1c /Settings/Settings1/Setting1c
2 /Settings/Settings1/Setting1c /Settings/Settings1/Setting1c
3 /Settings/Settings1/Setting1c /Settings/Settings1/Setting1c
1 /Settings/Settings2/Setting2a /Settings/Settings2/Setting2a
2 /Settings/Settings2/Setting2a /Settings/Settings2/Setting2a
3 /Settings/Settings2/Setting2a /Settings/Settings2/Setting2a
What is wrong with the S.Settings.value('(sql:column("NodePath"))[1]', 'varchar(max)')
line?
The XQuery you are trying to use is effectively being run dynaimcally. Unfortunately, you cannot use dynamic XQuery in SQL Server. Each XQuery must be static.
What you could do in your specific situation, is to break up each node predicate into separate columns. Then in the XQuery you can descend to the relevant node by checking each column. For example:
declare @settings table (RowId int identity, Settings xml)
insert @settings (Settings) values ('<settings><Settings1><Settings1a><Setting1a1>1-1a1</Setting1a1></Settings1a><Setting1b>1-1b</Setting1b><Setting1c>1-1c</Setting1c></Settings1><Settings2><Setting2a>1-2a</Setting2a></Settings2></settings>')
insert @settings (Settings) values ('<settings><Settings1><Settings1a><Setting1a1>2-1a1</Setting1a1></Settings1a><Setting1b>2-1b</Setting1b><Setting1c>2-1c</Setting1c></Settings1><Settings2><Setting2a>2-2a</Setting2a></Settings2></settings>')
insert @settings (Settings) values ('<settings><Settings1><Settings1a><Setting1a1>3-1a1</Setting1a1></Settings1a><Setting1b>3-1b</Setting1b><Setting1c>3-1c</Setting1c></Settings1><Settings2><Setting2a>3-2a</Setting2a></Settings2></settings>')
declare @nodesToFind table (NodePath1 nvarchar(max), NodePath2 nvarchar(max), NodePath3 nvarchar(max), NodePath4 nvarchar(max))
insert @nodesToFind (NodePath1, NodePath2, NodePath3, NodePath4) values ('settings','Settings1','Settings1a','Setting1a1')
insert @nodesToFind (NodePath1, NodePath2, NodePath3, NodePath4) values ('settings','Settings1','Setting1b',null)
insert @nodesToFind (NodePath1, NodePath2, NodePath3, NodePath4) values ('settings','Settings1','Setting1c',null)
insert @nodesToFind (NodePath1, NodePath2, NodePath3, NodePath4) values ('settings','Settings2','Setting2a',null)
select
S.RowId,
NTF.*,
S.Settings.value('((
for $i1 in *[local-name() = sql:column("NodePath1")]
return
if (empty(sql:column("NodePath2")))
then $i1
else for $i2 in ($i1/*[local-name() = sql:column("NodePath2")])
return
if (empty(sql:column("NodePath3")))
then $i2
else for $i3 in $i2/*[local-name() = sql:column("NodePath3")]
return
if (empty(sql:column("NodePath4")))
then $i3
else $i3/*[local-name() = sql:column("NodePath4")]
)/text())[1]', 'varchar(max)')
from @settings S
cross join @nodesToFind NTF
As you can see, it is made significantly more complex (and probably slow) by the fact that there are multiple possible node levels. If you can restrict it to only one level of node then you can remove the if
else
sections.