次の単純なXMLについて考えてみます。
<xml>
<customer name="Max">
<email address="[email protected]" />
</customer>
<customer name="Erik">
<email address="[email protected]" />
</customer>
<customer name="Brent">
<email address="brentcom" />
</customer>
</xml>
<Customer>
アイテムのaddress
属性が実行する<email>
シーケンスのリストを取得したいnot@
が含まれています。
したがって、次のような出力が必要です。
<customer name="Brent">
<email address="brentcom" />
</customer>
mcve :
DECLARE @x XML = '<xml>
<customer name="Max"><email address="[email protected]" /></customer>
<customer name="Erik"><email address="[email protected]" /></customer>
<customer name="Brent"><email address="brentcom" /></customer>
</xml>';
このクエリ:
SELECT WithValidEmail = @x.query('/xml/customer/email[contains(@address, "@")]')
, WithInvalidEmail = @x.query('/xml/customer/email[contains(@address, "@")] = False');
戻り値:
╔═══════════════════════════════════════╦══════════════════╗
║ WithValidEmail ║ WithInvalidEmail ║
╠═══════════════════════════════════════╬══════════════════╣
║ <email address="[email protected]" /> ║ ║
║ <email address="[email protected]" /> ║ false ║
╚═══════════════════════════════════════╩══════════════════╝
このクエリ:
SELECT WithInValidEmail = @x.query('/xml/customer/email')
WHERE @x.exist('/xml/customer/email[contains(@address, "@")]') = 0;
戻り値:
╔══════════════════╗
║ WithInValidEmail ║
╚══════════════════╝
(no results)
上記のクエリのWHERE
句は、電子メールアドレスに「@」記号が含まれている場所に少なくとも1つのシーケンスが存在するため、XMLのセット全体を排除しています。
これを行う簡単な方法は、nodes
method を使用してaddress
属性に直接アクセスし、@
記号を確認することです。
現在の見方の問題は、anyのメールアドレスに@
が含まれていることのみを確認していることです。 XMLノードを解析して、個々の電子メールをチェックできます。
DECLARE @x XML
= '<xml>
<customer name="Max"><email address="[email protected]" /></customer>
<customer name="Erik"><email address="[email protected]" /></customer>
<customer name="Brent"><email address="brentcom" /></customer>
</xml>';
SELECT x.c.value('@address', 'VARCHAR(100)') AS [email]
FROM @x.nodes('/xml/customer/email') AS x(c)
WHERE x.c.exist('@address[contains(., "@")]') = 0;
このようなXML列を持つ実際のテーブルをクエリする必要がある場合は、次のように、nodesメソッドをCROSS APPLY
するだけです。
SELECT x.c.value('@address', 'VARCHAR(100)') AS [email]
FROM @x_table AS xt
CROSS APPLY xt.x.nodes('/xml/customer/email') AS x(c)
WHERE x.c.exist('@address[contains(., "@")]') = 0;
その「行」のすべての<customer>...</customer>
XMLを戻したい場合は、軸を戻すことができます。ウォーキングbackにより、大きなXMLブロックのパフォーマンスが少し不安定になる可能性があることに注意してください。
SELECT x.c.query('..')
FROM @x_table AS xt
CROSS APPLY xt.x.nodes('/xml/customer/email') AS x(c)
WHERE x.c.exist('@address[contains(., "@")]') = 0;
それを行う別の方法は次のとおりです。
SELECT @x.query('/xml/customer[email/@address[not(contains(., "@"))]]') answer
大括弧を移動してEメール・ノードをラップすると、WHERE
節がcustomer
ノードに効果的に適用されます。このXQueryを英語に翻訳すると、次のようになります。
xml/customer
シンボルを含まないaddress
ノードを持つすべての@
ノードを取得してください
あなたはとても近かった。 .query()
関数の使用とcontains
XQuery関数の使用は間違いなく正しい方向に進んでいます。あなたが間違ったことは:
= False
の[...]
outsideを置く(つまり、contains()
式の一部ではなかった)false()
の代わりにWord False
を使用する/..
を追加して親ノードを指定しない(結果に<customer>
要素だけでなく<email>
要素が含まれるようにするため)これら3つのことを修正すると、次のXQuery式が得られ、必要なものが得られます。
'/xml/customer/email[contains(@address, "@") = false()]/..'
それを質問の元の例に入れると、次のようになります。
DECLARE @x XML = '<xml>
<customer name="Max"><email address="[email protected]" /></customer>
<customer name="Erik"><email address="[email protected]" /></customer>
<customer name="Brent"><email address="brentcom" /></customer>
</xml>';
SELECT
@x.query('/xml/customer/email[contains(@address, "@")]/..') AS [WithValidEmail],
@x.query('/xml/customer/email[contains(@address, "@")=false()]/..') AS [WithInvalidEmail;
このクエリは、2つのXMLフィールドを持つ単一行の次の結果セットを返します。
WithValidEmail | WithInvalidEmail
<customer name="Max"> | <customer name="Brent">
<email address="[email protected]" /> | <email address="brentcom" />
</customer> | </customer>
<customer name="Erik"> |
<email address="[email protected]" /> |
</customer> |
これは、.nodes()
関数を使用してドキュメントを分割するよりもおそらく効率的です。XMLをシングルショットで解析でき、ノードごとにパーサーを開始および停止する必要がないためです。
.query()
内に保持するもう1つの利点は、単一のXMLドキュメントが返されることです。したがって、複数のノードに相当するものを含むXMLドキュメント/値を受け取った場合、結果のノードをドキュメントに再構築する必要なく、単一のエンティティであるというスカラー値アプローチを維持できます。これにより、返される期待される行数を変更せずに、サブクエリ/ CTEで使用することもできます。