2013-06-27 9 views
6

Ho alcune proprietà di un'applicazione che mi vengono passate in formato XML. Devo analizzare la proprietà per nome e assegnare il valore alla colonna appropriata nel mio database.Analizza XML usando T-SQL e XQUERY - Ricerca di valori specifici

Attualmente sto analizzando in un componente di script SSIS ma ci vuole molto tempo per completare. Speravo che ci sarebbe stata una soluzione semplice per questo utilizzando XQUERY, ma non riesco a trovare quello che sto cercando.

Ecco un esempio di XML che sto ricevendo:

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

Quindi, se guardassi al primo elemento proprietà che sarebbe assegnare il valore predefinito a mia colonna DISMISS_SETTING nel mio database. Inoltre, è importante notare che l'ordine e le combinazioni dei valori possono verificarsi senza un ordine specifico.

risposta

9

Utilizzare la value() Method (xml Data Type) per estrarre un valore dal vostro XML. Cerca il nome che desideri in un predicato nell'espressione 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

Se siete alla ricerca di una soluzione TSQL e se il vostro tavolo risultato dovrebbe apparire così indicato sul schemat di seguito:

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

si dovrebbe usare insieme di script, verrà descritto in un attimo. Inizialmente è necessario creare una stored procedure dinamica che costruisca query dinamiche - ti dà la possibilità di inserire i tuoi dati nella tabella sotto tali colonne, i cui nomi non sono noti fino al runtime (il tempo del tuo parsing 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 

Avanti, utilizzare questa istruzione XQuery/SQL per estrarre (da XML) le informazioni che state cercando:

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

Dopo di che, le coppie di dati estratti [name, value] sono memorizzati nella tabella delle variabili @T . Infine, iterare su tali metadati temporanea e inserire valori in appositi nomi delle colonne della vostra tabella principale:

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 

soluzione presentata permette di avere il numero mutevole di nodi nel XML, forniti senza alcun ordine specifico.

Si noti che la logica responsabile dell'estrazione dei dati da XML e l'inserimento nella tabella principale, può essere racchiusa all'interno di una stored procedure aggiuntiva, ad es. mysp_xml_update (@data xml) e quindi eseguito nel seguente modo pulito: exec mysp_xml_update N'<properties>....</properties>.

Tuttavia, provare il codice utilizzando SQL Fiddle.

UPDATE:

Come richiesto nel commento - un unico grande aggiornamento deve essere eseguito, invece di colonna di aggiornamento in sequenza da colonna. A tale scopo, è necessario modificare mysp_update ad es. nel modo seguente:

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, questo è grande. C'è un modo semplice per farlo senza usare la variabile XML? Ad esempio, ho righe su righe di XML da elaborare. –

+0

@Dave L. Ciao, piacere di sentirlo. Purtroppo temo di non capire veramente la domanda in commento - puoi approfondire un po 'di più cosa stai cercando di ottenere? – jwaliszko

+0

invece di elaborare un record alla volta, c'è un modo per farlo senza dover scorrere tutti i record nel mio database? –

1

È possibile eseguire questa operazione estraendo il nome e il valore da xml e ruotando attorno al nome. Tuttavia, non è possibile farlo con nomi arbitrari trovati al momento della query. Se ti serve, probabilmente stai meglio rimuovendo il PIVOT e semplicemente usando le colonne nome e valore fornite dalla query interna.

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

ho deciso di aggiornare la mia risposta attuale (solo per la curiosità di alternative e scopi educativi). Ho spinto un'altra per mantenere entrambe le versioni e preservare la possibilità di tracciare le parti che sono state migliorate:

  1. Aggiornamento del primo approccio - sequenziale inserto/aggiornamento per ogni colonna (utilizzo del cursore, la rimozione di ridondante temporanea tabella):

    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. aggiornamento del secondo approccio - rinfusa inserimento/aggiornamento:

    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. E finale, completamente nuovo, terzo approccio (bulk dinamico unione con perno, nessun cicli, nessun cursori):

    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    
    

L'utilizzo segue:

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