同様の構造のデータを含むXML
列があります。
<Root>
<Elements>
<Element Code="1" Value="aaa"></Element>
<Element Code="2" Value="bbb"></Element>
<Element Code="3" Value="ccc"></Element>
</Elements>
</Root>
SQL Serverを使用してデータを変更し、各Value
属性を要素に変更するにはどうすればよいですか?
<Root>
<Elements>
<Element Code="1">
<Value>aaa</Value>
</Element>
<Element Code="2">
<Value>bbb</Value>
</Element>
<Element Code="3">
<Value>ccc</Value>
</Element>
</Elements>
</Root>
更新:
私のXMLは次のようになります。
<Root attr1="val1" attr2="val2">
<Elements>
<Element Code="1" Value="aaa" ExtraData="extra" />
<Element Code="2" Value="bbb" ExtraData="extra" />
<Element Code="3" Value="ccc" ExtraData="extra" />
<Element Code="4" Value="" ExtraData="extra" />
<Element Code="5" ExtraData="extra" />
</Elements>
<ExtraData>
<!-- Some XML is here -->
</ExtraData>
</Root>
Value
属性を移動し、他のすべての属性と要素を保持したいだけです。
XMLを細断処理し、XQueryを使用して再構築できます。
declare @X xml = '
<Root attr1="val1" attr2="val2">
<Elements>
<Element Code="1" Value="aaa" ExtraData="extra" />
<Element Code="2" Value="" ExtraData="extra" />
<Element Code="3" ExtraData="extra" />
</Elements>
<ExtraData>
<!-- Some XML is here -->
</ExtraData>
</Root>';
select @X.query('
(: Create element Root :)
element Root
{
(: Add all attributes from Root to Root :)
/Root/@*,
(: create element Elements under Root :)
element Elements
{
(: For each Element element in /Root/Elements :)
for $e in /Root/Elements/Element
return
(: Add element Element :)
element Element
{
(: Add all attributes except Value to Element :)
$e/@*[local-name() != "Value"],
(: Check if Attribute Value exist :)
if (data($e/@Value) != "")
then
(: Create a Value element under Element :)
element Value
{
(: Add attribute Value as data to the element Element :)
data($e/@Value)
}
else () (: Empty element :)
}
},
(: Add all childelements to Root except the Elements element :)
/Root/*[local-name() != "Elements"]
}');
結果:
<Root attr1="val1" attr2="val2">
<Elements>
<Element Code="1" ExtraData="extra">
<Value>aaa</Value>
</Element>
<Element Code="2" ExtraData="extra" />
<Element Code="3" ExtraData="extra" />
</Elements>
<ExtraData>
<!-- Some XML is here -->
</ExtraData>
</Root>
Elements
がRoot
の下の最初の要素でない場合、Elements
の前のすべての要素とElements
の後のすべての要素を追加するようにクエリを変更する必要があります。
XMLデータ型のメソッド(例 modify )といくつかのXQueryを使用してxmlを変更することもできます。
DECLARE @x XML = '<Root attr1="val1" attr2="val2">
<Elements>
<Element Code="1" Value="aaa" ExtraData="extra" />
<Element Code="2" Value="bbb" ExtraData="extra" />
<Element Code="3" Value="ccc" ExtraData="extra" />
</Elements>
<ExtraData>
<!-- Some XML is here -->
</ExtraData>
</Root>'
SELECT 'before' s, DATALENGTH(@x) dl, @x x
-- Add 'Value' element to each Element which doesn't already have one
DECLARE @i INT = 0
WHILE @x.exist('Root/Elements/Element[not(Value)]') = 1
BEGIN
SET @x.modify( 'insert element Value {data(Root/Elements/Element[not(Value)]/@Value)[1]} into (Root/Elements/Element[not(Value)])[1]' )
SET @i += 1
IF @i > 99 BEGIN RAISERROR( 'Too many loops...', 16, 1 ) BREAK END
END
-- Now delete all Value attributes
SET @x.modify('delete Root/Elements/Element/@Value' )
SELECT 'after' s, DATALENGTH(@x) dl, @x x
この方法は、XMLの大きな部分を適切にスケーリングする傾向はありませんが、XMLの大規模な置き換えよりも適している場合があります。
XMLがテーブルに保存されている場合は、この方法を簡単に適応させることもできます。繰り返しになりますが、100万行のテーブルに対して単一の更新を実行することはお勧めしません。テーブルが大きい場合は、テーブルを介してカーソルを実行するか、更新をバッチ処理することを検討してください。ここにテクニックがあります:
DECLARE @t TABLE ( rowId INT IDENTITY PRIMARY KEY, yourXML XML )
INSERT INTO @t ( yourXML )
SELECT '<Root attr1="val1" attr2="val2">
<Elements>
<Element Code="1" Value="aaa" ExtraData="extra" />
<Element Code="2" Value="bbb" ExtraData="extra" />
<Element Code="3" Value="ccc" ExtraData="extra" />
</Elements>
<ExtraData>
<!-- Some XML is here -->
</ExtraData>
</Root>'
INSERT INTO @t ( yourXML )
SELECT '<Root attr1="val1" attr2="val2">
<Elements>
<Element Code="21" Value="uuu" ExtraData="extra" />
<Element Code="22" Value="vvv" ExtraData="extra" />
<Element Code="23" Value="www" ExtraData="extra" />
<Element Code="24" Value="xxx" ExtraData="extra" />
<Element Code="25" Value="yyy" ExtraData="extra" />
<Element Code="26" Value="zzz" ExtraData="extra" />
</Elements>
<ExtraData>
<!-- Some XML is here -->
</ExtraData>
</Root>'
SELECT 'before' s, DATALENGTH(yourXML) dl, yourXML
FROM @t
-- Add 'Value' element to each Element which doesn't already have one
DECLARE @i INT = 0
WHILE EXISTS ( SELECT * FROM @t WHERE yourXML.exist('Root/Elements/Element[not(Value)]') = 1 )
BEGIN
UPDATE @t
SET yourXML.modify( 'insert element Value {data(Root/Elements/Element[not(Value)]/@Value)[1]} into (Root/Elements/Element[not(Value)])[1]' )
SET @i += 1
IF @i > 99 BEGIN RAISERROR( 'Too many loops...', 16, 1 ) BREAK END
END
-- Now delete all Value attributes
UPDATE @t
SET yourXML.modify('delete Root/Elements/Element/@Value' )
SELECT 'after' s, DATALENGTH(yourXML) dl, yourXML
FROM @t
UPDATE:
@Mikaelの細かい answer のコメントに記載されている最新の要件を反映するように、コードと以下のクエリ例の入力および出力XMLを更新しました。
@Valueが空または存在しない場合にValue要素を作成しない
単一の式はこの新しいバリエーションに正しく一致しますが、条件付きロジックは置換文字列で許可されないため、単一のパスで空の<Value/>
要素を省略する方法はないようです。そのため、これを2つの部分の変更に改造しました。1つは空でない@Value
属性を取得するためのパスで、もう1つは空の@Value
属性を取得するためのパスです。いずれにせよ<Element>
要素を持たないことが望ましいため、@Value
属性のない<Value>
sを処理する必要はありませんでした。
1つのオプションは、XMLを通常の文字列として扱い、パターンに基づいて変換することです。これは、SQLCLRコードを介して利用できる正規表現(特に「置換」関数)を使用して簡単に実現できます。
以下の例では、 SQL# ライブラリのRegEx_ReplaceスカラーUDFを使用しています(これは私が作成したものですが、このRegEx関数他の多くと一緒に無料版で利用可能です):
DECLARE @SomeXml XML;
SET @SomeXml = N'<Root attr1="val1" attr2="val2">
<Elements>
<Element Code="1" Value="aaa" ExtraData="extra1" />
<Element Code="22" Value="bbb" ExtraData="extra2" />
<Element Code="333" Value="ccc" ExtraData="extra3" />
<Element Code="4444" Value="" ExtraData="extra4" />
<Element Code="55555" ExtraData="extra5" />
</Elements>
<ExtraData>
<Something Val="1">qwerty A</Something>
<Something Val="2">qwerty B</Something>
</ExtraData>
</Root>';
DECLARE @TempStringOfXml NVARCHAR(MAX),
@Expression NVARCHAR(4000),
@Replacement NVARCHAR(4000);
SET @TempStringOfXml = CONVERT(NVARCHAR(MAX), @SomeXml);
PRINT N'Original: ' + @TempStringOfXml;
---
SET @Expression =
N'(<Element Code="[^"]+")\s+Value="([^"]+)"\s+(ExtraData="[^"]+")\s*/>';
SET @Replacement = N'$1 $3><Value>$2</Value></Element>';
SELECT @TempStringOfXml = SQL#.RegEx_Replace(@TempStringOfXml, @Expression,
@Replacement, -1, 1, '');
PRINT '-------------------------------------';
PRINT N'Phase 1: ' + @TempStringOfXml; -- transform Elements with a non-empty @Value
---
SET @Expression = N'(<Element Code="[^"]+")\s+Value=""\s+(ExtraData="[^"]+")\s*/>';
SET @Replacement = N'$1 $2 />';
SELECT @TempStringOfXml = SQL#.RegEx_Replace(@TempStringOfXml, @Expression,
@Replacement, -1, 1, '');
PRINT '-------------------------------------';
PRINT N'Phase 2: ' + @TempStringOfXml; -- transform Elements with an empty @Value
SELECT CONVERT(XML, @TempStringOfXml); -- prove that this is valid XML
PRINT
ステートメントは、[メッセージ]タブで簡単に並べて比較できるようにするためにあります。結果の出力は次のとおりです(元のXMLを少し変更して、目的の部分だけが変更され、それ以外は何も変更されていないことを非常に明確にしました)。
Original: <Root attr1="val1" attr2="val2"><Elements><Element Code="1" Value="aaa" ExtraData="extra1"/><Element Code="22" Value="bbb" ExtraData="extra2"/><Element Code="333" Value="ccc" ExtraData="extra3"/><Element Code="4444" Value="" ExtraData="extra4"/><Element Code="55555" ExtraData="extra5"/></Elements><ExtraData><Something Val="1">qwerty A</Something><Something Val="2">qwerty B</Something></ExtraData></Root>
-------------------------------------
Phase 1: <Root attr1="val1" attr2="val2"><Elements><Element Code="1" ExtraData="extra1"><Value>aaa</Value></Element><Element Code="22" ExtraData="extra2"><Value>bbb</Value></Element><Element Code="333" ExtraData="extra3"><Value>ccc</Value></Element><Element Code="4444" Value="" ExtraData="extra4"/><Element Code="55555" ExtraData="extra5"/></Elements><ExtraData><Something Val="1">qwerty A</Something><Something Val="2">qwerty B</Something></ExtraData></Root>
-------------------------------------
Phase 2: <Root attr1="val1" attr2="val2"><Elements><Element Code="1" ExtraData="extra1"><Value>aaa</Value></Element><Element Code="22" ExtraData="extra2"><Value>bbb</Value></Element><Element Code="333" ExtraData="extra3"><Value>ccc</Value></Element><Element Code="4444" ExtraData="extra4" /><Element Code="55555" ExtraData="extra5"/></Elements><ExtraData><Something Val="1">qwerty A</Something><Something Val="2">qwerty B</Something></ExtraData></Root>
テーブルのフィールドを更新する場合は、上記を次のように変更できます。
DECLARE @NonEmptyValueExpression NVARCHAR(4000),
@NonEmptyValueReplacement NVARCHAR(4000),
@EmptyValueExpression NVARCHAR(4000),
@EmptyValueReplacement NVARCHAR(4000);
SET @NonEmptyValueExpression =
N'(<Element Code="[^"]+")\s+Value="([^"]+)"\s+(ExtraData="[^"]+")\s*/>';
SET @NonEmptyValueReplacement = N'$1 $3><Value>$2</Value></Element>';
SET @EmptyValueExpression =
N'(<Element Code="[^"]+")\s+Value=""\s+(ExtraData="[^"]+")\s*/>';
SET @EmptyValueReplacement = N'$1 $2 />';
UPDATE tbl
SET XmlField = SQL#.RegEx_Replace4k(
SQL#.RegEx_Replace4k(
CONVERT(NVARCHAR(4000), tbl.XmlField),
@NonEmptyValueExpression,
@NonEmptyValueReplacement,
-1, 1, ''),
@EmptyValueExpression,
@EmptyValueReplacement,
-1, 1, '')
FROM SchemaName.TableName tbl
WHERE tbl.XmlField.exist('Root/Elements/Element/@Value') = 1;
SQL Serverの外部でそれを行うには、おそらくより良い方法があります。ただし、これを行う1つの方法があります。
あなたのデータ:
declare @xml xml = N'<Root>
<Elements>
<Element Code="1" Value="aaa"></Element>
<Element Code="2" Value="bbb"></Element>
<Element Code="3" Value="ccc"></Element>
</Elements>
</Root>';
クエリ:
With xml as (
Select
Code = x.e.value('(@Code)', 'varchar(10)')
, Value = x.e.value('(@Value)', 'varchar(10)')
From @xml.nodes('/Root//Elements/Element') as x(e)
)
Select * From (
Select code
, (
Select value
From xml x1 where x1.Code = Element.Code
For xml path(''), elements, type
)
From xml Element
For xml auto, type
) as Root(Elements)
for xml auto, elements;
Xml CTEは、xml変数をテーブルに変換します。
次に、メインの選択によってCTEがxmlに変換されます。
出力:
<Root>
<Elements>
<Element code="1">
<value>aaa</value>
</Element>
<Element code="2">
<value>bbb</value>
</Element>
<Element code="3">
<value>ccc</value>
</Element>
</Elements>
</Root>
For XML Explicit
を使用して行うこともできます。