【问题标题】:Deleting Multiple Nodes in Single XQuery for SQL Server在 SQL Server 的单个 XQuery 中删除多个节点
【发布时间】:2010-11-04 10:52:43
【问题描述】:

我有:

  1. 具有 xml 类型列(ID 列表)的表
  2. xml 类型参数(也是 ID 列表)

从列中删除与参数中的节点匹配的节点,同时保留任何不匹配的节点的最佳方法是什么?

例如

declare @table table (
    [column] xml
)

insert @table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')

declare @parameter xml
set @parameter = '<r><i>1</i><i>2</i></r>'

-- this is the problem
update @table set [column].modify('delete (//i *where text() matches @parameter*)')

MSDN 文档表明它应该是可能的(Introduction to XQuery in SQL Server 2005):

这个存储过程很容易被 修改为接受 XML 片段 其中包含一项或多项技能 元素,从而允许用户 删除多个技能节点 存储过程的单次调用。

【问题讨论】:

    标签: sql-server xml sql-server-2005 xpath xquery


    【解决方案1】:

    您需要以下形式的内容:

    [column].modify('delete (//i[.=(1, 2)])') -- like SQL IN
    -- or
    [column].modify('delete (//i[.=1 or .=2])')
    -- or
    [column].modify('delete (//i[.=1], //i[.=2])')
    -- or
    [column].modify('delete (//i[contains("|1|2|",concat("|",.,"|"))])')
    

    XQuery 在 SQL2005 中不支持 xml SQL 类型,modify 方法只接受字符串字面量(不允许使用变量)。

    这是一个带有 contains 函数的丑陋 hack:

    declare @table table ([column] xml)
    insert @table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')
    
    declare @parameter xml
    set @parameter = '<r><i>1</i><i>2</i></r>'
    
    -- build a pipe-delimited string
    declare @in nvarchar(max)
    set @in = convert(nvarchar(max),
        @parameter.query('for $i in (/r/i) return concat(string($i),"|")')
      )
    set @in = '|'+replace(@in,'| ','|')
    
    update @table set [column].modify ('
      delete (//i[contains(sql:variable("@in"),concat("|",.,"|"))])
      ')
    select * from @table
    

    这是另一个带有动态 SQL 的代码:

    -- replace table variable with temp table to get around variable scoping
    if object_id('tempdb..#table') is not null drop table #table
    create table #table ([column] xml)
    insert #table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')
    
    declare @parameter xml
    set @parameter = '<r><i>1</i><i>2</i></r>'
    
    -- we need dymamic SQL because the XML modify method only permits string literals
    declare @sql nvarchar(max)
    set @sql = convert(nvarchar(max), 
        @parameter.query('for $i in (/r/i) return concat(string($i),",")')
      )
    set @sql = substring(@sql,1,len(@sql)-1)
    set @sql = 'update #table set [column].modify(''delete (//i[.=('+@sql+')])'')'
    print @sql
    exec (@sql)
    
    select * from #table
    

    如果您要更新 xml 变量而不是列,请使用 sp_executesql 和输出参数:

    declare @xml xml
    set @xml = '<r><i>1</i><i>2</i><i>3</i></r>'
    
    declare @parameter xml
    set @parameter = '<r><i>1</i><i>2</i></r>'
    
    declare @sql nvarchar(max)
    set @sql = convert(nvarchar(max), 
        @parameter.query('for $i in (/r/i) return concat(string($i),",")')
      )
    set @sql = substring(@sql,1,len(@sql)-1)
    set @sql = 'set @xml.modify(''delete (//i[.=('+@sql+')])'')'
    exec sp_executesql @sql, N'@xml xml output', @xml output
    
    select @xml
    

    使用游标迭代删除值的替代方法,由于多次更新,效率可能较低:

    declare @table table ([column] xml)
    insert @table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')
    
    declare @parameter xml
    set @parameter = '<r><i>1</i><i>2</i></r>'
    
    /*
    -- unfortunately, this doesn't work:
    update t set [column].modify('delete (//i[.=sql:column("p.i")])')
    from @table t, (
      select i.value('.', 'nvarchar')
      from @parameter.nodes('//i') a (i)
      ) p (i)
    select * from @table
    */
    
    -- so we have to use a cursor
    declare @cursor cursor
    set @cursor = cursor for
      select i.value('.', 'varchar') as i
      from @parameter.nodes('//i') a (i)
    
    declare @i int
    open @cursor
    while 1=1 begin
      fetch next from @cursor into @i
      if @@fetch_status <> 0 break
      update @table set [column].modify('delete (//i[.=sql:variable("@i")])')
    end
    
    select * from @table
    

    更多关于SQL2005中XML变量限制的信息在这里:

    http://blogs.msdn.com/denisruc/archive/2006/05/17/600250.aspx

    谁有更好的办法?

    【讨论】:

      【解决方案2】:

      虽然以这种方式删除有点尴尬,但您可以通过更新来更改数据,前提是您的数据很简单(例如您提供的示例)。以下查询基本上会将两个 XML 字符串拆分为表,将它们连接起来,排除非空(匹配)值,并将其转换回 XML:

      UPDATE @table 
      SET [column] = (
          SELECT p.i.value('.','int') AS c
          FROM [column].nodes('//i') AS p(i)
          OUTER APPLY (
              SELECT x.i.value('.','bigint') AS i
              FROM @parameter.nodes('//i') AS x(i)
              WHERE p.i.value('.','bigint') = x.i.value('.','int')
          ) a
          WHERE a.i IS NULL
          FOR XML PATH(''), TYPE
      )
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-30
        • 1970-01-01
        • 2021-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多