Gibt XML-Sequenzen zurück, in denen ein Attribut kein bestimmtes Zeichen enthält

10

Betrachten Sie das folgende einfache 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>

Ich möchte eine Liste von bekommen <Customer>Sequenzen , in denen das addressAttribut des <email>Elements ist nicht ein enthalten @.

Ich möchte also eine Ausgabe, die wie folgt aussieht:

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

Diese Abfrage:

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

Kehrt zurück:

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

Diese Abfrage:

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

Kehrt zurück:

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

Die WHEREKlausel in der obigen Abfrage eliminiert den gesamten XML-Satz, da mindestens eine einzelne Sequenz vorhanden ist, in der die E-Mail-Adresse ein "@" -Zeichen enthält.

Max Vernon
quelle

Antworten:

11

Eine einfache Möglichkeit, dies zu tun, besteht darin, die nodes Methode zu verwenden, um direkt zum addressAttribut zu gelangen und nach Ihrem @Zeichen zu suchen.

Das Problem bei der Art und Weise, wie Sie jetzt suchen, besteht darin, dass nur überprüft wird, ob eine E-Mail-Adresse eine enthält @. Durch Parsen der XML-Knoten können Sie einzelne E-Mails darauf überprüfen.

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;

Wenn Sie eine tatsächliche Tabelle mit einer XML-Spalte wie dieser abfragen müssen, würden Sie nur CROSS APPLYdie Knotenmethode wie folgt verwenden:

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;

Wenn Sie das gesamte <customer>...</customer>XML für diese "Zeile" zurückbringen möchten , können Sie die Achse zurückgehen. Seien Sie sich bewusst sein , dass das Gehen zurück Leistung ein bisschen Woogy für große XML - Blöcke machen.

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;

Eine andere Möglichkeit ist:

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

Wenn Sie die eckigen Klammern verschieben, um den E-Mail-Knoten zu umschließen, wird die WHEREKlausel effektiv auf den customerKnoten angewendet . Die Übersetzung dieser XQuery ins Englische sieht folgendermaßen aus:

Holen Sie mir alle xml/customerKnoten mit einem emailKnoten, der ein addressAttribut hat, das das @Symbol nicht enthält

Erik Darling
quelle
4

Du warst so nah dran. Sie waren mit der Verwendung der .query()Funktion und der containsXQuery-Funktion definitiv auf dem richtigen Weg . Was Sie falsch verstanden haben war:

  1. Das = False Äußere von setzen [...](was bedeutet, es war nicht Teil des contains()Ausdrucks)
  2. Verwenden Sie das Wort Falseanstelle der Funktionfalse()
  3. Keine Angabe des übergeordneten Knotens durch Hinzufügen /..am Ende des Pfads (sodass das Ergebnis das <customer>Element und nicht nur das <email>Element enthält)

Wenn Sie diese drei Dinge korrigieren, erhalten Sie den folgenden XQuery-Ausdruck, mit dem Sie das bekommen, was Sie wollen:

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

Wenn Sie dies aus der Frage in Ihr ursprüngliches Beispiel einfügen, erhalten Sie:

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;

Diese Abfrage gibt die folgende Ergebnismenge einer einzelnen Zeile mit zwei XML-Feldern zurück:

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

Dies ist wahrscheinlich effizienter als das Aufteilen des Dokuments mit der .nodes()Funktion, da es das XML in einem einzigen Schuss analysieren kann und nicht den Parser für jeden Knoten starten und stoppen muss.

Der andere Vorteil der Beibehaltung .query()besteht darin, dass Sie ein einzelnes XML-Dokument zurückerhalten. Wenn Sie also ein XML-Dokument / einen XML-Wert erhalten, der mehrere Knoten enthält, können Sie den skalaren Wertansatz beibehalten, dass es sich um eine einzelne Entität handelt, ohne die resultierenden Knoten erneut in ein Dokument rekonstruieren zu müssen. Auf diese Weise können Sie es auch in einer Unterabfrage / einem CTE verwenden, ohne die Anzahl der erwarteten Zeilen zu ändern, die zurückgegeben werden.

Solomon Rutzky
quelle