【问题标题】:The argument 1 of the XML data type method "value" must be a string literalXML 数据类型方法“value”的参数 1 必须是字符串字面量
【发布时间】:2016-06-29 16:25:41
【问题描述】:

我已通读 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 的值(为什么?)。我一直在搞砸它,但无法让它做正确的事情。

有人想吗?

【问题讨论】:

  • 请在此处使用实际的 SO URL。您可以右键单击任何问题或答案底部的share 链接并选择Copy,它将按标题正确链接(就像我在上面对您的 URL 所做的那样)。谢谢。

标签: xml tsql


【解决方案1】:

您的选择返回 @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 注入、验证输入等)

【讨论】:

  • 我很不走运,因为我试图在 UDF 中执行此操作。 grrr ...(谢谢)
  • hmm... 或者,我可以执行/my/path/mytag[@someattr=sql:variable("@myattr")]... 之类的操作,那么如何创建一个查找给定路径/标签的查询,而不是在标签中查找属性?
  • 您可以这样做:选择@xml.value('(//*[local-name() = sql:variable("@elementname")])[1]', 'nvarchar(max)'),与@elementname = 'element'。也许通过一些黑客攻击,您可以通过路径查找它,但如果不知道更多细节,我无法确定。
  • 我已经走到了一半,但一直在弄清楚节点的路径是什么。我在这里发帖:stackoverflow.com/questions/12414233/…
  • 我已取消选中您的回复作为答案,因为即使您回答了我关于为什么返回 @path 的值的问题,核心问题仍未得到解答。但是,我有一个解决方案,我在这里发布了答案。不过,您的回复会得到支持。非常感谢。
【解决方案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

甜!

【讨论】:

    【解决方案3】:

    如果您只需要按名称查找子元素并想从 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 变量来运行它们。我测试了第一个语句,但没有明确测试其他 2 个语句,仅供参考。

    【讨论】:

    • 但关键是我不想硬编码元素的路径
    • 使用第二条语句中建议的格式,我使用 local-name() 比使用 name(.) 运气好。这种格式比使用交叉应用更好、更整洁,这是肯定的。
    猜你喜欢
    • 2020-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-07
    • 2018-11-18
    • 2022-08-21
    • 2023-02-14
    • 2021-06-02
    相关资源
    最近更新 更多