在单个XQuery中为SQL Server删除多个节点

时间:2009-06-19 22:25:11

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

我有:

  1. 包含 xml 类型列(ID列表)
  2. 的表格
  3. xml 类型参数(也是ID列表)
  4. 从列中删除与参数中的节点匹配的节点的最佳方法是什么,同时保持任何不匹配的节点不变?

    e.g。

    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片段   其中包含一项或多项技能   从而允许用户进行   用a删除多个技能节点   单个调用存储过程。

2 个答案:

答案 0 :(得分:3)

您需要表单中的内容:

[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方法只接受字符串文字(不允许变量)。

这是一个丑陋的hack w / contains函数:

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

这是另一个w /动态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

有没有人有更好的方法?

答案 1 :(得分:3)

虽然以这种方式执行删除操作有点尴尬,但如果您的数据很简单(例如您提供的示例),则可以执行更新以更改数据。以下查询基本上将两个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
)