XML DML ステートメントを使用して、型指定されていない XML 列の要素の名前を変更することはできますか?
XML 列の XML スキーマ コレクションを更新中です。最新のスキーマを適用する前に、1 つの要素の名前を変更して既存の XML インスタンスにパッチを適用する必要があります。
ドキュメントからわかる限り、ノードを挿入/削除するか、それらの値を置き換えることしかできません。
XML DML ステートメントを使用して、型指定されていない XML 列の要素の名前を変更することはできますか?
XML 列の XML スキーマ コレクションを更新中です。最新のスキーマを適用する前に、1 つの要素の名前を変更して既存の XML インスタンスにパッチを適用する必要があります。
ドキュメントからわかる限り、ノードを挿入/削除するか、それらの値を置き換えることしかできません。
ことわざにあるように、「意志あるところに道あり」
ここに 2 つの方法があります。1 つ 目は、以前の xml を、元の xml から新しい要素名で構築された新しい xml に単純に置き換えることです。私の例では、Legs/Leg を Limbs/Limb に変更しました。これは、最も単純なスキーマ以外では非常に複雑になる可能性があります。
そして第二に、挿入と削除を組み合わせるより適切なアプローチです。
それらを 1 つの簡単な例にまとめました。
declare @xml as xml = '<animal species="Mouse">
<legs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</legs>
</animal>'
set @xml = (select
t.c.value('@species', 'varchar(max)') as '@species'
,(select
ti.C.value('.', 'varchar(max)')
from @Xml.nodes('//animal/legs/leg') ti(c) for xml path('limb'), /* root('limb'), */type) as limbs
from @xml.nodes('//*:animal') t(c) for xml path('animal'), type)
select @xml;
while (@xml.exist('/animal/limbs/limb') = 1) begin
/*insert..*/
set @xml.modify('
insert <leg>{/animal/limbs/limb[1]/text()}</leg>
before (/animal/limbs/limb)[1]
');
/*delete..*/
set @xml.modify('delete (/animal/limbs/limb)[1]');
end
set @xml.modify('
insert <legs>{/animal/limbs/leg}</legs>
before (/animal/limbs)[1]
');
set @xml.modify('delete (/animal/limbs)[1]');
select @xml;
SQL Serverユニットテストの開発中に(ssut-関連するブログ投稿を参照)、テストされたオブジェクトからのxmlセットを標準化したかったのです。テストしたオブジェクトを複数回呼び出すので、毎回セット名とレコード名は同じになります。読みやすくするために、元のレコードのレコードセットにはと同様<original_record_set><original_record /></original_record_set>
の名前を付け、テストレコードのレコードセットにはと同様の名前を付ける必要があり<test_record_set><test_record /></ test_record_set >
ます。
テストされたオブジェクトの呼び出しを最初に変更できる場合は、明らかにこれを行うのは簡単です。
SET @output = (SELECT col1, col2
FROM @test_object_result
FOR xml path ( test_record '), root( test_record_set '));
その後:
SET @output = (SELECT col1, col2
FROM @test_object_result
FOR xml path ( original_record'), root( original_record_set '));
ただし、同じオブジェクトを複数回呼び出しており、「for xml path」では、path('...')
およびroot('...')
メソッドで変数を使用できないため、別のメソッドを考え出す必要がありました。
この関数は、xmlツリーを受け入れて新しいツリーを構築し、ルートノードをの値に置き換え、@relation_name
各レコードの名前を。に置き換えます@tuple_name
。新しいツリーは、レコードごとに異なる数がある場合でも、元のツリーのすべての属性を使用して構築されます。
例外
明らかに、これは複数の要素レベルでは機能しません!以下の例に示すように、単一レベルの属性ベースのツリーを処理するために特別に構築しました。将来的にはマルチレベルの混合属性/要素ツリー用に構築する可能性がありますが、以下の基本的な問題を解決したので、その方法が明らかになったと思います。その演習は読者に任せます。その時まで保留中。
USE [unit_test];
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[standardize_record_set]') AND type IN ( N'FN', N'IF', N'TF', N'FS', N'FT' ))
DROP FUNCTION [dbo].[standardize_record_set];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
SET nocount ON;
GO
/*
DECLARE
@relation_name nvarchar(150)= N'standardized_record_set',
@tuple_name nvarchar(150)= N'standardized_record',
@xml xml,
@standardized_result xml;
SET @xml='<Root>
<row id="12" two="now1" three="thr1" four="four1" />
<row id="232" two="now22" three="thr22" />
<row id="233" two="now23" three="thr23" threeextra="extraattrinthree" />
<row id="234" two="now24" three="thr24" fourextra="mealsoin four rwo big mone" />
<row id="235" two="now25" three="thr25" />
</Root>';
execute @standardized_result = [dbo].[standardize_record_set] @relation_name=@relation_name, @tuple_name=@tuple_name, @xml=@xml;
select @standardized_result;
*/
CREATE FUNCTION [dbo].[standardize_record_set] (@relation_name nvarchar(150)= N'record_set',
@tuple_name nvarchar(150)= N'record', @xml xml )
returns XML
AS
BEGIN
DECLARE
@attribute_index int = 1,
@attribute_count int = 0,
@record_set xml = N'<' + @relation_name + ' />',
@record_name nvarchar(50) = @tuple_name,
@builder nvarchar(max),
@record xml,
@next_record xml;
DECLARE @record_table TABLE (
record xml );
INSERT INTO @record_table
SELECT t.c.query('.') AS record
FROM @xml.nodes('/*/*') T(c);
DECLARE record_table_cursor CURSOR FOR
SELECT cast([record] AS xml)
FROM @record_table
OPEN record_table_cursor
FETCH NEXT FROM record_table_cursor INTO @next_record
WHILE @@FETCH_STATUS = 0
BEGIN
SET @attribute_index=1;
SET @attribute_count = @next_record.query('count(/*[1]/@*)').value('.', 'int');
SET @builder = N'<' + @record_name + N' ';
-- build up attribute string
WHILE @attribute_index <= @attribute_count
BEGIN
SET @builder = @builder + @next_record.value('local-name((/*/@*[sql:variable("@attribute_index")])[1])',
'varchar(max)') + '="' + @next_record.value('((/*/@*[sql:variable("@attribute_index")])[1])',
'varchar(max)') + '" ';
SET @attribute_index = @attribute_index + 1
END
-- build record and add to record_set
SET @record = @builder + ' />';
SET @record_set.modify('insert sql:variable("@record") into (/*)[1]');
FETCH NEXT FROM record_table_cursor INTO @next_record
END
CLOSE record_table_cursor;
DEALLOCATE record_table_cursor;
RETURN @record_set;
END;
GO
はい、DML を使用して、名前を変更したいノードで要素を切り取り、その要素に新しいノードを挿入し、切り取った要素をそのノードの xml に貼り付けて、要素の名前を変更できます。デモ用に SQL フィドルを実行しました。http://sqlfiddle.com/#!3/dc64d/1 これは変更されます
<animal species="Mouse">
<legs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</legs>
</animal>
の中へ
<animal species="Mouse">
<armsandlegs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</armsandlegs>
</animal>
SqlFiddle は、私のソリューションを壊してから長い間経過しているようです。記憶から、私のソリューションの基礎を以下に貼り付けました...
DECLARE @XML2 xml
DECLARE @XML3 xml = '<limbs></limbs>'
DECLARE @XML xml =
'<animal species="Mouse">
<legs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</legs>
</animal>'
SET @XML2 = @XML.query('animal/legs/*')
SET @XML.modify('
insert
(sql:variable("@XML3"))
after
(/animal/legs)[1]
')
SET @XML.modify('
delete (/animal/legs[1])
')
SET @XML.modify('
insert
(sql:variable("@XML2"))
as last into
(/animal/limbs)[1]
')
select @XML