web-dev-qa-db-fra.com

Voir si un élément XML existe à n'importe quel niveau du document avec une valeur spécifique

Est-il possible d'interroger XML pour trouver si un élément particulier a une certaine valeur? Par exemple, si je voulais voir si le XML ci-dessous a la valeur "Brandt" dans <ContactFName>.

Mais notez que l'emplacement de l'élément peut changer. Dans certains cas, il peut être dans /root/MCTLocations/MCTLocation, ou il peut sauter sous la racine, ou apparaître ailleurs ...

Et, est-il possible de paramétrer le nom de l'élément?

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

Pour cela, vous voulez utiliser la fonction XML .exist() car elle renverra une valeur BIT (c'est-à-dire booléenne) indiquant si XQuery trouve ou non quelque chose.

Pour gérer l'emplacement non statique d'un élément, vous utiliseriez soit * (indiquant qu'il doit vérifier tous les nœuds d'un niveau particulier, mais pas les autres niveaux), ou // (indiquant qu'il doit vérifier tous les nœuds à ce niveau et en dessous).

Les exemples suivants utilisent l'exemple de requête de la question comme base, et ajoutent quelques cas de test pour placer l'élément à différents niveaux, et ajoute un cas de test qui change le nom pour montrer que le XQuery ne se contente pas de tout sélectionner.

Configuration du test (exécuté une fois)

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>
');

Test 1 (* à la place d'un nom de nœud)

Cela vérifiera tous les nœuds au niveau spécifié, qui dans ce cas est juste sous <root>. Mais il ne vérifiera pas les autres niveaux.

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

Renvoie la ligne avec ID valeur de 3.

Test 2 (* à la place d'un nom de nœud)

Cela vérifiera tous les nœuds au niveau spécifié, qui dans ce cas est juste sous <root><MCTLocations>. Mais il ne vérifiera pas les autres niveaux.

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

Renvoie des lignes avec ID valeurs de 1 et 5.

Test 3 (// à la place d'un nom de nœud)

Cela vérifiera tous les nœuds commençant au niveau spécifié, qui dans ce cas est juste sous <root><MCTLocations>, et ci-dessous .

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

Renvoie des lignes avec ID valeurs de 1, 4 et 5.

Test 4 (/* ou */ à la place d'un nom de nœud)

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;

Les deux renvoient des lignes avec des valeurs ID de 1, 3, 4 et 5.

Ceux-ci ne renvoient pas l'ID de ligne 6 en raison du * étant un espace réservé pour un seul nœud, le niveau le plus élevé autorisé serait donc sous <root> (ou tout nœud de niveau supérieur).

Test 5 (// au niveau supérieur)

Cela vérifiera tous les nœuds commençant au niveau supérieur.

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

Renvoie des lignes avec ID valeurs de 1, 3, 4, 5 et 6.

Test 6 (utilisez la valeur de variable locale pour le texte de l'élément dans 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;

La première requête renvoie des lignes avec des valeurs ID de 1, 3, 4, 5 et 6.

La deuxième requête renvoie une ligne avec ID valeur 2.

Test 7 (utilisez la fonction et le littéral de chaîne pour le nom d'élément dans XQuery)

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

Renvoie des lignes avec ID valeurs de 5 et 6.

Test 8 (utilisez la fonction avec la valeur de variable locale pour le nom de l'élément dans XQuery)

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

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

Renvoie la ligne avec ID valeur de 4.

Test 9 (assembler toutes les pièces)

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;

Renvoie des lignes avec ID valeurs de 1, 3, 4, 5 et 6.


Remarque XML générale:

Les données XML (dans SQL Server) sont encodées en UTF-16 Little Endian, comme NVARCHAR/NCHAR. Par conséquent, il est préférable de préfixer les littéraux sting avec un majuscule -N lorsque la valeur est vraiment XML.

13
Solomon Rutzky