Zwraca sekwencje XML, w których atrybut nie zawiera określonego znaku

10

Rozważ następujący prosty 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>

Chcę uzyskać listę <Customer>sekwencji, w których addressatrybut <email>elementu nie zawiera @.

Tak więc chcę, aby dane wyjściowe wyglądały następująco:

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

To zapytanie:

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

Zwroty:

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

To zapytanie:

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

Zwroty:

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

WHEREKlauzula kwerendy powyżej jest wyeliminowanie cały zestaw XML, ponieważ co najmniej jedna sekwencja występuje wówczas, gdy adres e-mail zawiera znak „@”.

Max Vernon
źródło

Odpowiedzi:

11

Prostym sposobem na to jest skorzystanie z nodes metody, aby przejść bezpośrednio do addressatrybutu i sprawdzić swój @znak.

Problem z tym, jak teraz patrzysz, polega na tym, że sprawdza tylko, czy ma w nim jakiś adres e-mail @. Przetwarzanie węzłów XML pozwala na sprawdzenie poszczególnych e-maili.

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;

Jeśli potrzebujesz zapytać o rzeczywistą tabelę z taką kolumną XML, po prostu CROSS APPLYzastosuj metodę nodes:

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;

Jeśli chcesz przywrócić wszystkie dane <customer>...</customer>XML dla tego „wiersza”, możesz cofnąć oś. Pamiętaj tylko, że cofnięcie się może sprawić, że wydajność będzie nieco myląca dla dużych bloków 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;

Innym sposobem na to jest:

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

Przesunięcie nawiasów kwadratowych w celu zawinięcia węzła e-mail skutecznie powoduje, że WHEREklauzula zostanie zastosowana do customerwęzła. Tłumaczenie tego XQuery na angielski wygląda następująco:

Zdobądź wszystkie xml/customerwęzły z emailwęzłem, który ma addressatrybut, który nie zawiera @symbolu

Erik Darling
źródło
4

Byłaś tak blisko. Zdecydowanie byłeś na dobrej drodze, korzystając z .query()funkcji i containsfunkcji XQuery. To, co pomyliłeś, to:

  1. Umieszczenie = False na zewnątrz z [...](co oznacza, że nie był częścią contains()wypowiedzi)
  2. Używanie słowa Falsezamiast funkcjifalse()
  3. Nieokreślanie węzła nadrzędnego przez dodanie /..na końcu ścieżki (aby wynik zawierał <customer>element, a nie tylko <email>element)

Poprawienie tych trzech rzeczy skutkuje następującym wyrażeniem XQuery, które zapewnia ci to, czego chcesz:

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

Umieszczenie tego w swoim oryginalnym przykładzie z pytania daje:

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;

To zapytanie zwraca następujący zestaw wyników jednego wiersza z dwoma polami 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>                               |

Jest to prawdopodobnie bardziej wydajne niż dzielenie dokumentu za pomocą .nodes()funkcji, ponieważ może on analizować XML w jednym ujęciu i nie musi uruchamiać i zatrzymywać analizatora składni dla każdego węzła.

Inną zaletą trzymania go w środku jest .query()to, że zwracany jest pojedynczy dokument XML. Tak więc, jeśli otrzymasz dokument / wartość XML zawierający wiele rzeczy o wartości wielu węzłów, możesz zachować podejście oparte na wartości skalarnej, jako że jest ono pojedynczym bytem, ​​bez konieczności ponownego odtwarzania uzyskanych węzłów z powrotem do dokumentu. Pozwala to również na użycie go w podzapytaniu / CTE bez zmiany liczby zwracanych oczekiwanych wierszy.

Solomon Rutzky
źródło