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>
したがって、最初のプロパティ要素を確認している場合は、データベースのDISMISS_SETTING列に値DEFAULTを割り当てます。また、値の順序と組み合わせが特定の順序で発生する可能性があることに注意することが重要です。
value()メソッド(xmlデータ型) を使用して、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
TSQLソリューションを探している場合、結果テーブルは次のスキーマに示すようになります。
_| 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 を使用して自分でコードを試してください。
更新:
コメントで要求されているように、列ごとに順次更新するのではなく、1つの大きな更新を実行する必要があります。そのためには、_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
_
これを行うには、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
私は既存の答えを更新することにしました(代替案の好奇心と教育目的のためだけに)。両方のバージョンを保持し、改善されたパーツを追跡する可能性を維持するために、別のバージョンをプッシュしました。
最初のアプローチの更新-各列の順次挿入/更新(カーソルの使用、冗長な一時テーブルの削除):
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>'