web-dev-qa-db-fra.com

Renvoie des séquences xml où un attribut ne contient pas de caractère spécifique

Considérez le XML simple suivant:

<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>

Je veux obtenir une liste de <Customer> séquences où l'attribut address de <email> l'élément ne contient pas un @.

Donc, je veux une sortie qui ressemble à:

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

Cette requête:

SELECT WithValidEmail = @x.query('/xml/customer/email[contains(@address, "@")]')
    , WithInvalidEmail = @x.query('/xml/customer/email[contains(@address, "@")] = False');

Retour:

╔═══════════════════════════════════════╦══════════════════╗
║            WithValidEmail             ║ WithInvalidEmail ║
╠═══════════════════════════════════════╬══════════════════╣
║ <email address="[email protected]" />        ║                  ║
║ <email address="[email protected]" /> ║ false            ║
╚═══════════════════════════════════════╩══════════════════╝

Cette requête:

SELECT WithInValidEmail = @x.query('/xml/customer/email')
WHERE @x.exist('/xml/customer/email[contains(@address, "@")]') = 0;

Retour:

╔══════════════════╗
║ WithInValidEmail ║
╚══════════════════╝
    (no results)

La clause WHERE dans la requête ci-dessus élimine l'ensemble complet de XML car au moins une seule séquence existe où l'adresse e-mail contient un signe "@".

10
Max Vernon

Un moyen simple de le faire est d'utiliser la nodesméthode pour accéder directement à l'attribut address et vérifier votre @ signe.

Le problème avec la façon dont vous regardez maintenant est qu'il vérifie uniquement que toute adresse e-mail a un @ dedans. L'analyse des nœuds XML vous permet de vérifier les e-mails individuels pour cela.

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;

Si vous devez interroger une table réelle avec une colonne XML comme celle-ci, vous devez simplement CROSS APPLY la méthode des nœuds comme suit:

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;

Si vous voulez apporter tous les <customer>...</customer> XML pour cette "ligne" en arrière, vous pouvez reculer l'axe. Sachez simplement que marcher vers l'arrière peut rendre les performances un peu louches pour les gros blocs 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;

Une autre façon de procéder est:

SELECT @x.query('/xml/customer[email/@address[not(contains(., "@"))]]') answer

En déplaçant les crochets pour envelopper le nœud de courrier électronique, la clause WHERE s'applique effectivement au nœud customer. La traduction de cette XQuery en anglais ressemble à ceci:

Obtenez-moi tout xml/customer nœuds avec un nœud email qui a un attribut address qui ne contient pas le @ symbole

11
Erik Darling

Tu étais si proche. Vous étiez définitivement sur la bonne voie en utilisant la fonction .query() et en utilisant la fonction XQuery contains. Ce que vous vous êtes trompé, c'est:

  1. Mettre le = Falseextérieur du [...] (Ce qui signifie qu'il ne faisait pas partie de l'expression contains())
  2. Utiliser le mot False au lieu de la fonction false()
  3. Ne pas spécifier le nœud parent en ajoutant /.. À la fin du chemin (de sorte que le résultat inclura l'élément <customer> Et pas seulement l'élément <email>)

La correction de ces trois choses se traduit par l'expression XQuery suivante qui vous obtient ce que vous voulez:

'/xml/customer/email[contains(@address, "@") = false()]/..'

Mettre cela dans votre exemple original de la question vous donne:

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;

Cette requête renvoie l'ensemble de résultats suivant d'une seule ligne avec deux champs 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>                               |

C'est probablement plus efficace que de décomposer le document avec la fonction .nodes() car elle peut analyser le XML en une seule fois et n'a pas besoin de démarrer et d'arrêter l'analyseur pour chaque nœud.

L'autre avantage de le conserver dans .query() est que vous obtenez un seul document XML retourné. Donc, si vous recevez un document/une valeur XML contenant plusieurs nœuds, vous pouvez conserver l'approche de la valeur scalaire comme s'il s'agissait d'une seule entité sans avoir à reconstruire les nœuds résultants dans un document. Cela vous permet également de l'utiliser dans une sous-requête/CTE sans modifier le nombre de lignes attendues renvoyées.

4
Solomon Rutzky