2013-06-27 29 views
6

Tôi có một số thuộc tính của một ứng dụng được chuyển cho tôi dưới dạng XML. Tôi cần phân tích thuộc tính theo tên và gán giá trị cho cột thích hợp trong cơ sở dữ liệu của tôi.Phân tích cú pháp XML bằng T-SQL và XQUERY - Tìm kiếm các giá trị cụ thể

Tôi hiện đang phân tích cú pháp đó trong thành phần tập lệnh SSIS nhưng phải mất nhiều thời gian để hoàn thành. Tôi đã hy vọng sẽ có một giải pháp dễ dàng cho việc này bằng cách sử dụng XQUERY, nhưng tôi không thể tìm thấy những gì tôi đang tìm kiếm.

Dưới đây là một ví dụ về xml Tôi nhậ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> 

Vì vậy, nếu tôi đang nhìn vào các yếu tố bất động sản đầu tiên tôi sẽ giao DEFAULT giá trị cho cột DISMISS_SETTING của tôi trong cơ sở dữ liệu của tôi. Ngoài ra, điều quan trọng cần lưu ý là thứ tự và kết hợp của các giá trị có thể đi qua không theo thứ tự cụ thể.

Trả lời

9

Sử dụng value() Method (xml Data Type) để trích xuất một giá trị từ XML của bạn. Kiểm tra tên bạn muốn trong một vị từ trong biểu thức 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

Nếu bạn đang tìm kiếm một giải pháp TSQL và nếu tôi bảng kết quả của bạn sẽ trông như thế này được hiển thị trên schemat dưới đây:

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

bạn nên sử dụng thiết lập các kịch bản tôi sẽ mô tả trong một khoảnh khắc. Ban đầu, bạn cần tạo thủ tục lưu trữ động để tạo truy vấn động - nó cung cấp cho bạn khả năng chèn dữ liệu của bạn vào bảng dưới các cột như vậy, tên không được biết cho đến khi chạy (thời gian phân tích cú pháp XML của bạn):

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 

Tiếp theo, sử dụng câu lệnh XQuery/SQL này để trích xuất (từ XML) thông tin mà bạn đang tìm kiếm:

-- 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) 

Sau đó, cặp dữ liệu chiết xuất [tên, giá trị] được lưu trữ trong bảng biến @T . Cuối cùng, lặp qua siêu dữ liệu tạm thời như vậy và chèn giá trị trong tên thích hợp cột của bảng chính của bạn:

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 

giải pháp trình bày cho phép bạn có số có thể thay đổi các nút trong XML, với điều kiện không có thứ tự cụ thể.

Lưu ý rằng logic chịu trách nhiệm về trích xuất dữ liệu từ XML và chèn vào bảng chính, có thể được bao bọc trong quy trình được lưu trữ bổ sung, ví dụ: mysp_xml_update (@data xml) và sau đó được thực hiện theo cách sạch sẽ sau: exec mysp_xml_update N'<properties>....</properties>.

Tuy nhiên, hãy tự mình thử mã sử dụng SQL Fiddle.

UPDATE:

Theo yêu cầu trong các bình luận - một bản cập nhật lớn nên được thực hiện thay vì cột liên tục cập nhật theo cột. Vì mục đích đó, cần phải sửa đổi mysp_update ví dụ: theo cách sau:

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

Jaroslaw, đây là tuyệt quá. Có cách nào dễ dàng để làm điều này mà không sử dụng biến XML? Ví dụ, tôi có các hàng trên các hàng XML để xử lý. –

+0

@Dave L. Xin chào, rất vui được nghe điều đó. Thật không may tôi sợ tôi không thực sự hiểu được câu hỏi trong bình luận - bạn có thể xây dựng thêm một chút những gì bạn đang cố gắng để đạt được? – jwaliszko

+0

thay vì xử lý một bản ghi tại một thời điểm, có cách nào để thực hiện việc này mà không phải lặp qua từng bản ghi trong cơ sở dữ liệu của tôi không? –

1

Bạn có thể thực hiện việc này bằng cách trích xuất tên và giá trị từ xml và xoay quanh tên. Tuy nhiên, bạn không thể làm điều này với các tên tùy ý được tìm thấy tại thời điểm truy vấn. Nếu bạn cần điều đó, có lẽ bạn nên loại bỏ PIVOT và chỉ sử dụng các cột tên và giá trị được cung cấp bởi truy vấn bên trong.

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

tôi quyết định làm mới câu trả lời hiện tại của tôi (chỉ dành riêng cho sự tò mò của giải pháp thay thế và mục đích giáo dục). Tôi đẩy một số khác để giữ cho cả hai phiên bản và duy trì khả năng theo dõi những phần đã được cải thiện:

  1. Cập nhật các phương pháp tiếp cận đầu tiên - tuần tự chèn/cập nhật cho mỗi cột (sử dụng của con trỏ, loại bỏ các dư thừa tạm thời bảng):

    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. cập nhật của cách tiếp cận thứ hai - số lượng lớn chèn/cập nhật:

    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. Và cuối cùng, hoàn toàn mới, cách tiếp cận thứ ba (số lượng lớn năng động kết hợp với trục, không có vòng, không có con trỏ):

    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    
    

Việc sử dụng được sau:

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>'