2013-06-27 35 views
6

我有一些应用程序的特性以XML形式传递给我。我需要按名称解析属性,并将值分配给数据库中的相应列。使用T-SQL和XQUERY解析XML - 搜索特定值

我目前正在解析它在一个SSIS脚本组件,但它需要很长时间才能完成。我希望能有一个简单的解决方案,使用XQUERY,但我找不到我在找什么。

这是我收到的XML的一个例子:

<properties> 
    <property> 
     <name>DISMISS_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
    <property> 
     <name>SHOW_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
    <property> 
     <name>DEFAULT_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
</properties> 

所以,如果我在看第一属性元素我会分配值DEFAULT我DISMISS_SETTING列在我的数据库。此外,重要的是要注意,这些值的顺序和组合可能会以不特定的顺序出现。

回答

9

使用value() Method (xml Data Type)从XML中提取一个值。在XQuery表达式的谓词中检查您想要的名称。

select 
    @XML.value('(/properties/property[name = "DISMISS_SETTING"]/value/text())[1]', 'nvarchar(100)') as DISMISS_SETTING, 
    @XML.value('(/properties/property[name = "SHOW_SETTING"]/value/text())[1]', 'nvarchar(100)') as SHOW_SETTING, 
    @XML.value('(/properties/property[name = "DEFAULT_SETTING"]/value/text())[1]', 'nvarchar(100)') as DEFAULT_SETTING 

SQL Fiddle

1

如果你正在寻找一个解决方案TSQL,如果我是你的结果表应该是这样下面的schemat所示:

| DISMISS_SETTING | SHOW_SETTING | DEFAULT_SETTING | 
|-----------------|--------------|-----------------| 
| DEFAULT   | DEFAULT  | DEFAULT   | 

你应当使用脚本我将在稍后描述。首先,您需要创建动态存储过程,而建立动态查询 - 它给你的可能性,你的数据插入到表中这样的列,其名称是不知道,直到运行时(你的XML解析的时间)下:

create procedure mysp_update (@table_name nvarchar(50), @column_name nvarchar(50), @column_value nvarchar(50)) 
as 
begin 
    declare @rows_count int 
    declare @query nvarchar(500) 
    declare @parm_definition nvarchar(100) 

    -- Get rows count in your table using sp_executesql and an output parameter   
    set @query = N'select @rows_count = count(1) from ' + quotename(@table_name) 
    exec sp_executesql @query, N'@rows_count INT OUTPUT', @rows_count OUTPUT 

    -- If no rows - insert the first one, else - update existing 
    if @rows_count = 0 
     set @query = N'insert into ' + quotename(@table_name) + N'(' + quotename(@column_name) + N') values (@column_value)'   
    else 
     set @query = N'update ' + quotename(@table_name) + N'set ' + quotename(@column_name) + N' = @column_value' 

    set @parm_definition = N'@column_value nvarchar(50)' 
    exec sp_executesql @query, @parm_definition, @column_value = @column_value 
end 
go 

接下来,使用此的XQuery/SQL语句来提取(从XML)的信息,你正在寻找:

-- Define XML object based on which insert statement will be later created 
declare @data xml = N'<properties> 
    <property> 
     <name>DISMISS_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
    <property> 
     <name>SHOW_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
    <property> 
     <name>DEFAULT_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
</properties>' 

-- Declare temporary container 
declare @T table(id int identity, name nvarchar(50), value nvarchar(50)) 

-- Push the extracted nodes values into it 
insert into @T(name, value) 
select 
    x.value(N'(name)[1]', N'nvarchar(50)'), 
    x.value(N'(value)[1]', N'nvarchar(50)') 
from 
    @data.nodes(N'/properties/property') AS XTbl(x) 

之后,提取数据对[名称,值]存储在表变量@T 。最后,迭代此类临时元数据,并在适当的列名的主表的插入

declare @name nvarchar(50), @value nvarchar(50), @current_id int = 1 

-- Fetch first row 
select @name = name, @value = value 
from @T where id = @current_id 

while @@rowcount = 1 
begin 
    -- Execute SP here (btw: SP cannot be executed from select statement) 
    exec mysp_update N'TableName', @name, @value 

    -- Fetch next row 
    set @current_id = @current_id + 1 

    select @name = name, @value = value 
    from @T where id = @current_id 
end 

提出的解决方案可以让你有在XML节点的可变数目,没有任何特定的顺序提供。

请注意,负责从XML中提取数据并插入到主表中的逻辑可以封装在附加的存储过程中,例如, mysp_xml_update (@data xml)然后按照以下清理方式执行:exec mysp_xml_update N'<properties>....</properties>

尽管如此,您可以使用SQL Fiddle自己尝试编码。

UPDATE:

的要求,在评论 - 一个大的更新应该由列执行的,而不是按顺序更新列。为此目的,mysp_update应该被修改,例如,在以下方式中:

create type HashTable as table(name nvarchar(50), value nvarchar(50)) 
go 

create procedure mysp_update (@table_name nvarchar(50), @set HashTable readonly) 
as 
begin 
    -- Concatenate names and values (to be passed to insert statement below) 
    declare @columns varchar(max) 
    select @columns = COALESCE(@columns + ', ', '') + quotename(name) from @set 
    declare @values varchar(max) 
    select @values = COALESCE(@values + ', ', '') + quotename(value, '''') from @set 

    -- Remove previous values 
    declare @query nvarchar(500) 
    set @query = N'delete from ' + quotename(@table_name) 
    -- Insert new values to the table 
    exec sp_executesql @query 
    set @query = N'insert into ' + quotename(@table_name) + N'(' + @columns + N') values (' + @values + N')'  
    exec sp_executesql @query 
end 
go 
+0

雅罗斯瓦夫,这是大。有没有简单的方法来做到这一点,而不使用XML变量?例如,我有一行处理XML的行。 –

+0

@Dave L.嗨,很高兴听到这一点。不幸的是,我担心我并不真正理解评论中的问题 - 你能详细阐述一下你想达到的目标吗? – jwaliszko

+0

而不是一次处理一条记录,有没有办法做到这一点,而不必迭代我的数据库中的每条记录? –

1

您可以通过从xml中提取名称和值以及关于名称的透视来完成此操作。但是,您无法在查询时使用任意名称来执行此操作。如果你需要的话,你可能最好删除PIVOT,并使用内部查询提供的名称和值列。

DECLARE @xml xml 

SET @xml = N'<properties> 
    <property> 
     <name>DISMISS_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
    <property> 
     <name>SHOW_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
    <property> 
     <name>DEFAULT_SETTING</name> 
     <value>DEFAULT</value> 
    </property> 
</properties>' 

SELECT  [DISMISS_SETTING], [SHOW_SETTING], [DEFAULT_SETTING] 
FROM  (
       SELECT  properties.property.value(N'./name[1]', N'nvarchar(MAX)') AS propertyName 
         , properties.property.value(N'./value[1]', N'nvarchar(MAX)') AS propertyValue 
       FROM  @xml.nodes(N'/properties/property') AS properties(property) 
      ) AS properties 
      PIVOT (MIN(propertyValue) FOR propertyName IN ([DISMISS_SETTING], [SHOW_SETTING], [DEFAULT_SETTING])) AS settings 
1

我决定去刷新我的现有应答(只是替代品和教育目的的好奇心)。我把另一个保留两个版本,并保持跟踪这是提高了零件的可能性:

第一种方法的
  1. 更新 - 每列顺序插入/更新(游标的使用 ,去除多余的临时的表):

    create procedure mysp_update (@table_name nvarchar(50), @column_name nvarchar(50), @column_value nvarchar(50)) 
    as 
    begin 
        set nocount on; 
        declare @rows_count int 
        declare @query nvarchar(500) 
        declare @parm_definition nvarchar(100) = N'@column_value nvarchar(50)'   
    
        -- Update the row if it exists 
        set @query = N'update ' + quotename(@table_name) + N'set ' + quotename(@column_name) + N' = @column_value' 
        exec sp_executesql @query, @parm_definition, @column_value = @column_value   
        -- Insert the row if the update statement failed 
        if (@@rowcount = 0) 
        begin 
         set @query = N'insert into ' + quotename(@table_name) + N'(' + quotename(@column_name) + N') values (@column_value)' 
         exec sp_executesql @query, @parm_definition, @column_value = @column_value 
        end 
    end 
    go 
    
    create procedure mysp_xml_update (@table_name nvarchar(50), @data xml) 
    as 
    begin 
        set nocount on;    
        declare @name nvarchar(50), @value nvarchar(50) 
    
        -- Declare optimized cursor (fast_forward specifies forward_only, read_only cursor with performance optimizations enabled) 
        declare mycursor cursor fast_forward 
        for select 
         x.value(N'(name)[1]', N'nvarchar(50)'), 
         x.value(N'(value)[1]', N'nvarchar(50)') 
        from 
         @data.nodes(N'/properties/property') AS xtbl(x) 
    
         open mycursor 
         fetch next from mycursor into @name, @value 
         while @@fetch_status = 0 
         begin  
          -- Execute SP here (btw: SP cannot be executed from select statement) 
          exec mysp_update @table_name, @name, @value   
          -- Get the next row 
          fetch next from mycursor into @name, @value 
         end 
        close mycursor; 
        deallocate mycursor; 
    end 
    go 
    
  2. 第二种方法的更新 - 批量插入/更新:

    create procedure mysp_xml_update (@table_name nvarchar(50), @data xml) 
    as 
    begin 
        set nocount on;    
        declare @name nvarchar(50), @value nvarchar(50) 
    
        -- Declare optimized cursor (fast_forward specifies forward_only, read_only cursor with performance optimizations enabled) 
        declare mycursor cursor fast_forward 
        for select 
         x.value(N'(name)[1]', N'nvarchar(50)'), 
         x.value(N'(value)[1]', N'nvarchar(50)') 
        from 
         @data.nodes(N'/properties/property') AS xtbl(x) 
    
        declare @insert_statement nvarchar(max) = N'insert into ' + quotename(@table_name) + N' ($columns$) values (''$values$)' 
        declare @update_statement nvarchar(max) = N'update ' + quotename(@table_name) + N' set $column$=''$value$' 
    
        open mycursor 
        fetch next from mycursor into @name, @value 
        while @@fetch_status = 0 
        begin    
         set @insert_statement = replace(@insert_statement, '$columns$', quotename(@name) + ',$columns$') 
         set @insert_statement = replace(@insert_statement, '$values$', @value + ''',''$values$') 
         set @update_statement = replace(@update_statement, '$column$', quotename(@name)) 
         set @update_statement = replace(@update_statement, '$value$', @value + ''',$column$=''$value$') 
         fetch next from mycursor into @name, @value 
        end 
        close mycursor; 
        deallocate mycursor; 
    
        set @insert_statement = replace(@insert_statement, ',$columns$', '') 
        set @insert_statement = replace(@insert_statement, ',''$values$', '') 
        set @update_statement = replace(@update_statement, ',$column$=''$value$', '') 
    
        -- Update the row if it exists 
        exec sp_executesql @update_statement  
        -- Insert the row if the update statement failed 
        if (@@rowcount = 0) 
        begin   
         exec sp_executesql @insert_statement 
        end 
    end 
    go 
    
  3. create procedure mysp_xml_update (@table_name nvarchar(50), @data xml) 
    as 
    begin 
        set nocount on;  
        declare @columns nvarchar(max), @scolumns nvarchar(max), @kvp nvarchar(max)='', @query nvarchar(max) 
        select @columns = coalesce(@columns + ',', '') + quotename(x.value(N'(name)[1]', N'nvarchar(50)')), 
          @scolumns = coalesce(@scolumns + ',', '') + 's.' + quotename(x.value(N'(name)[1]', N'nvarchar(50)')), 
          @kvp = @kvp + quotename(x.value(N'(name)[1]', N'nvarchar(50)')) + '=s.' 
             + quotename(x.value(N'(name)[1]', N'nvarchar(50)')) + ',' 
        from @data.nodes(N'/properties/property') as xtbl(x) 
        select @kvp = left(@kvp, len(@kvp)-1) 
    
        set @query = ' 
    merge ' + quotename(@table_name) + ' t 
    using 
    (
        select ' + @columns + ' from 
        (
         select props.x.value(N''./name[1]'', N''nvarchar(50)'') as name, 
           props.x.value(N''./value[1]'', N''nvarchar(50)'') as value 
         from @data.nodes(N''/properties/property'') as props(x) 
        ) properties 
        pivot 
        (
         min(value) for name in (' + @columns + ') 
        ) settings 
    ) s (' + @columns + ') 
    on (1=1) 
    when matched then 
        update set ' + @kvp + ' 
    when not matched then 
        insert (' + @columns + ') 
        values (' + @scolumns + ');'  
    
        exec sp_executesql @query, N'@data xml', @data = @data 
    end 
    go    
    

的用法如下:和最后的,全新的,第三种方法(动态散装带支点,没有循环,没有游标合并)

exec mysp_xml_update N'mytable', N'<properties> 
             <property> 
              <name>DEFAULT_SETTING</name> 
              <value>NEW DEFAULT 3</value> 
             </property> 
             <property> 
              <name>SHOW_SETTING</name> 
              <value>NEW DEFAULT 2</value> 
             </property> 
            </properties>'