web-dev-qa-db-ja.com

XML要素が特定の値を持つドキュメントの任意のレベルに存在するかどうかを確認します

特定の要素に特定の値があるかどうかを確認するためにXMLをクエリすることは可能ですか?たとえば、以下のXMLの<ContactFName>の値が「ブランド」であるかどうかを確認したい場合は、.

ただし、要素の場所は変更される場合があります。場合によっては、/root/MCTLocations/MCTLocationにあるか、ルートの下にジャンプするか、別の場所に表示されることがあります...

そして、要素名をパラメータ化することは可能ですか?

DECLARE @table TABLE (XmlCol XML)

INSERT INTO @table (XmlCol) VALUES ('
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
        <ContactLName>Brandt</ContactLName>
    </MCTLocation>
</MCTLocations>
</root>')

SELECT * FROM @table WHERE ??
7
John Hennesey

このためには、XQueryが何かを検出するかどうかを示すBIT(つまりブール値)値を返すため、.exist() XML関数を使用します。

要素の静的でない場所を処理するには、*(特定のレベルのすべてのノードをチェックし、他のレベルはチェックしないことを示す)、または//(それが必要であることを示す)のいずれかを使用します。そのレベル以下のすべてのノードを確認してください)。

次の例では、質問のクエリ例をベースとして使用し、いくつかのテストケースを追加して要素をさまざまなレベルに配置し、名前を変更してXQueryがすべてを選択しているわけではないことを示すテストケースを追加します。

テスト設定(1回実行)

SET NOCOUNT ON;
CREATE TABLE #Table (ID INT NOT NULL, XmlCol XML);

INSERT INTO #Table (ID, XmlCol) VALUES (1, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
        <ContactLName>Brandt</ContactLName>
    </MCTLocation>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (2, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
        <ContactLName>Grandt</ContactLName>
    </MCTLocation>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (3, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
</MCTLocations>
<ContactLName>Brandt</ContactLName>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (4, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
    <NewElement>
       <SubElement>
          <ContactLName>Brandt</ContactLName>
       </SubElement>
    </NewElement>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (5, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
    <NewerElement>
       <ContactLName>Brandt</ContactLName>
    </NewerElement>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (6, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
    <NewerElement>
    </NewerElement>
</MCTLocations>
</root>
<ContactLName>Brandt</ContactLName>
');

テスト1(ノード名の代わりに*

これにより、指定したレベルのすべてのノードがチェックされます。この場合は、<root>のすぐ下にあります。しかし、それは他のレベルをチェックしません。

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'/*/ContactLName[text()="Brandt"]') = 1;

ID値が3の行を返します。

テスト2(ノード名の代わりに*

これにより、指定したレベルのすべてのノードがチェックされます。この場合は、<root><MCTLocations>のすぐ下にあります。しかし、それは他のレベルをチェックしません。

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'/root/MCTLocations/*/ContactLName[text()="Brandt"]') = 1;

ID値が1および5の行を返します。

テスト3(ノード名の代わりに//

これは、指定されたレベル(この場合は<root><MCTLocations>の直下)にあるすべてのノードstartingをチェックします以下

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'/root/MCTLocations//ContactLName[text()="Brandt"]') = 1;

ID値が1、4、および5の行を返します。

テスト4(ノード名の代わりに/*または*/

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'*//ContactLName[text()="Brandt"]') = 1;

-- and:

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//*/ContactLName[text()="Brandt"]') = 1;

どちらも、ID値が1、3、4、5の行を返します。

*が単一ノードのプレースホルダーであるため、これらは行ID 6を返しません。したがって、許可される最高レベルは<root>(または任意のトップレベルノード)の下になります。

テスト5(最上位の//

これにより、最上位のすべてのノードstartingがチェックされます。

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//ContactLName[text()="Brandt"]') = 1;

ID値が1、3、4、5、6の行を返します。

テスト6(XQueryの要素テキストにローカル変数値を使用)

DECLARE @Name NVARCHAR(50) = N'Brandt';

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//ContactLName[text()=sql:variable("@Name")]') = 1;

SET @Name = N'Grandt';

-- exact same query, just different value in the variable
SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//ContactLName[text()=sql:variable("@Name")]') = 1;

最初のクエリは、ID値が1、3、4、5、6の行を返します。

2番目のクエリは、ID値が2の行を返します。

テスト7(XQueryの要素名に関数と文字列リテラルを使用)

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//.[local-name()="NewerElement"]') = 1;

ID値が5および6の行を返します。

テスト8(XQueryの要素名にローカル変数値を使用して関数を使用)

DECLARE @Node NVARCHAR(50) = N'SubElement';

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//.[local-name()=sql:variable("@Node")]') = 1;

ID値が4の行を返します。

テスト9(すべてのピースを1つにまとめる)

DECLARE @NodeName NVARCHAR(50) = N'ContactLName',
        @NodeText NVARCHAR(500) = N'Brandt';

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//.[local-name()=sql:variable("@NodeName")]
   [text()=sql:variable("@NodeText")]') = 1;

ID値が1、3、4、5、6の行を返します。


一般的なXML注:

(SQL Serverの)XMLデータは、NVARCHAR/NCHARと同じように、UTF-16リトルエンディアンとしてエンコードされます。したがって、値が実際にXMLである場合は、文字列リテラルの前に大文字のNを付けるのが最善です。

13
Solomon Rutzky