2013-06-27 33 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>'