我读过SO:XML data type method “value” must be a string literal,但我的问题有点不同。我在一个变量中有一点xml,我想分开并给出一个路径。最初我试过这个:
declare @x xml
select @x = '....'
select @x.value('(' + @path + ')[1]', 'varchar(max)')
但是,当然,这失败了。然后我找到了sql:variable并尝试了这个:
select @x.value('(sql:variable("@path"))[1]', 'varchar(max)')
但奇怪地返回@path的值(为什么?)。我一直在搞乱它,但不能让它做正确的事。
有人想到吗?
答案 0 :(得分:3)
你的select返回@path
的值,因为sql:variable()
返回一个文字值,所以实际上你要求SQL服务器从文档中选择文字值@path
,它就是确实。我知道做你想做的事的唯一方法就是使用动态SQL,如下所示:
declare @xml xml = '
<root>
<element attr="test">blah</element>
</root>';
declare @p nvarchar(max) = '(//element/text())[1]';
declare @sql nvarchar(max)
= 'select @x.value(''' + @p + ''', ''nvarchar(max)'')';
exec sp_executesql @sql, @parameters = N'@x xml', @x = @xml;
但是我应该警告你,这不是很好的做法(考虑SQL注入,验证输入等)。
答案 1 :(得分:2)
在Microsoft网站上wBob的帮助下,我现在得到了一个干净的解决方案。当然,性能是一个问题,因为整个文档将被映射为单一路径,但改进仍留给读者作为建议的可能性:)
if object_id('VMConfigVal') is not null
drop function VMConfigVal
go
create function VMConfigVal(@x xml, @path varchar(max))
returns nvarchar(max)
as
begin
declare @ret nvarchar(max)
;with cte as
(
select value = x.c.value('.', 'varchar(50)')
, path = cast ( null as varchar(max) )
, node = x.c.query('.')
from @x.nodes('/*') x(c)
union all
select n.c.value('.', 'varchar(50)')
, isnull( c.path + '/', '/' )
+ n.c.value('local-name(.)', 'varchar(max)')
, n.c.query('*')
from cte c
cross apply c.node.nodes('*') n(c)
)
select @ret = value from cte where path = @path
return @ret
end
go
所以我现在可以这样做:
select dbo.VMConfigVal(MyXMLConfig, '/hardware/devices/IDE/ChannelCount')
from someTable
甜!
答案 2 :(得分:1)
如果您只需要按名称查找子元素,并希望从XPath文字中抽象出名称,则可以选择以下选项:
// Returns the /root/node/element/@Value with @Name contained in @AttributeName SQL variable.
SELECT @Xml.value('(/root/node/element[@Name=sql:variable("@AttributeName")]/@Value)[1]', 'varchar(100)')
// Returns the text of the child element of /root/node with the name contained in @ElementName SQL variable.
SELECT @Xml.value('(/root/node/*[name(.)=sql:variable("@ElementName")]/text())[1]', 'varchar(100)')
// Searching the xml hierarchy for elements with the name contained in @ElementName and returning the text().
SELECT @Xml.value('(//*[name(.)=sql:variable("@ElementName")]/text())[1]', 'varchar(100)')
您需要声明@ElementName或@AttributeName SQL变量来运行它们。我测试了第一个语句,但没有明确测试其他两个语句,FYI。