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

10

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' addressattribut de l' <email>élément ne contient pas de @.

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 WHEREclause de 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 "@".

Max Vernon
la source

Réponses:

11

Un moyen simple de le faire consiste à utiliser la nodes méthode pour accéder directement à l' addressattribut et vérifier votre @signe.

Le problème avec la façon dont vous regardez maintenant, c'est qu'il ne fait que vérifier que toute adresse e-mail contient un @. L'analyse des nœuds XML vous permet de vérifier les e-mails individuels.

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 APPLYla 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 ramener tout le <customer>...</customer>XML de cette "ligne", vous pouvez remonter l'axe. Sachez simplement que revenir en 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 messagerie, la WHEREclause s'applique effectivement au customernœud. La traduction de ce XQuery en anglais ressemble à ceci:

Obtenez-moi tous les xml/customernœuds avec un emailnœud qui a un addressattribut qui ne contient pas le @symbole

Erik Darling
la source
4

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

  1. Mettre l' = False extérieur du [...](c'est-à-dire qu'il ne faisait pas partie de l' contains()expression)
  2. Utiliser le mot Falseau lieu de la fonctionfalse()
  3. Ne pas spécifier le nœud parent en ajoutant /..à la fin du chemin (de sorte que le résultat inclura l' <customer>élément et pas seulement l' <email>élément)

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 le jeu 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 .nodes()fonction car il 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 .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 en tant qu'entité unique 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.

Solomon Rutzky
la source