Вернуть последовательности XML, где атрибут не содержит конкретного символа

10

Рассмотрим следующий простой XML:

<xml>
  <customer name="Max">
    <email address="me@you.com" />
  </customer>
  <customer name="Erik">
    <email address="erik@your-mom.com" />
  </customer>
  <customer name="Brent">
    <email address="brentcom" />
  </customer>
</xml>

Я хочу получить список <Customer>последовательностей, где addressатрибут <email>элемента не содержит @.

Итак, я хочу вывод, который выглядит так:

<customer name="Brent">
  <email address="brentcom" />
</customer>

MCVE :

DECLARE @x XML = '<xml>
<customer name="Max"><email address="me@you.com" /></customer>
<customer name="Erik"><email address="erik@your-mom.com" /></customer>
<customer name="Brent"><email address="brentcom" /></customer>
</xml>';

Этот запрос:

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

Возвращает:

╔═══════════════════════════════════════╦══════════════════╗
            WithValidEmail              WithInvalidEmail 
╠═══════════════════════════════════════╬══════════════════╣
 <email address="me@you.com" />                          
 <email address="erik@your-mom.com" />  false            
╚═══════════════════════════════════════╩══════════════════╝

Этот запрос:

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

Возвращает:

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

Предложение WHEREв приведенном выше запросе исключает весь набор XML, поскольку существует хотя бы одна последовательность, в которой адрес электронной почты содержит знак «@».

Макс Вернон
источник

Ответы:

11

Простой способ сделать это - использовать nodes метод, чтобы получить право на addressатрибут и проверить ваш @знак.

Проблема с тем, как вы выглядите сейчас, заключается в том, что он проверяет, есть ли в нем любой адрес электронной почты @. Разбор узлов XML позволяет вам проверять отдельные электронные письма.

DECLARE @x XML
    = '<xml>
<customer name="Max"><email address="me@you.com" /></customer>
<customer name="Erik"><email address="erik@your-mom.com" /></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;

Если вам нужно запросить фактическую таблицу со столбцом XML, подобным этому, вы просто должны CROSS APPLYиспользовать метод узлов следующим образом:

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;

Если вы хотите вернуть весь <customer>...</customer>XML для этой «строки» обратно, вы можете пройтись по оси назад. Просто имейте в виду, что откат назад может привести к снижению производительности больших блоков 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;

Еще один способ сделать это:

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

Перемещение квадратных скобок, чтобы обернуть вокруг узла электронной почты эффективно, делает это WHEREпредложение примененным к customerузлу. Перевод этого XQuery на английский выглядит так:

Получить мне все xml/customerузлы с emailузлом, который имеет addressатрибут, который не содержит @символ

Эрик Дарлинг
источник
4

Ты был так близко. Вы определенно были на правильном пути, используя .query()функцию и containsфункцию XQuery. То, что вы ошиблись, было:

  1. Ввод = False снаружи от [...]( а значит, это не было частью contains()выражения)
  2. Использование слова Falseвместо функцииfalse()
  3. Не указывать родительский узел, добавляя /..в конец пути (чтобы результат включал <customer>элемент, а не только <email>элемент)

Исправление этих трех вещей приводит к следующему выражению XQuery, которое дает вам то, что вы хотите:

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

Поместив это в свой исходный пример из вопроса, вы получите:

DECLARE @x XML = '<xml>
<customer name="Max"><email address="me@you.com" /></customer>
<customer name="Erik"><email address="erik@your-mom.com" /></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;

Этот запрос возвращает следующий результирующий набор из одной строки с двумя полями XML:

WithValidEmail                            |     WithInvalidEmail
<customer name="Max">                     |     <customer name="Brent">
  <email address="me@you.com" />          |       <email address="brentcom" />
</customer>                               |     </customer>
<customer name="Erik">                    |
  <email address="erik@your-mom.com" />   |
</customer>                               |

Это, вероятно, более эффективно, чем разбивать документ с помощью .nodes()функции, поскольку он может анализировать XML за один раз и не требует запуска и остановки анализатора для каждого узла.

Другое преимущество .query()- это то, что вы получите один XML-документ. Таким образом, если вы получаете XML-документ / значение, содержащее несколько узлов, вы можете сохранить подход скалярного значения, заключающийся в том, что он представляет собой единую сущность, без необходимости повторной реконструкции полученных узлов обратно в документ. Это также позволяет использовать его в подзапросе / CTE без изменения числа ожидаемых строк, возвращаемых.

Соломон Руцкий
источник