T SQL Фильтр XQuery для выбора только узлов, которые содержат хотя бы один символ (или не являются числами c) - PullRequest
1 голос
/ 12 марта 2020

У меня есть следующее XML.

<XML>   
    <Data>         
        <Outer>
            <Inner>123ABC</Inner>
        </Outer>
        <Outer>
            <Inner>123</Inner>
        </Outer>
        <Outer>
            <Inner>-123</Inner>
        </Outer>
    </Data>
</XML>

Я хочу выбрать содержимое узла <Inner>, но только если это не число c. Или другими словами: я хочу выбрать его, только если он содержит хотя бы один символ. Таким образом, результат должен быть только 123ABC.

Мой код SQL в настоящее время выглядит следующим образом.

DECLARE @string NVARCHAR(MAX);
DECLARE @xml XML;
SET @string = 
  '<XML><Data><Outer><Inner>123ABC</Inner></Outer><Outer><Inner>-123</Inner></Outer><Outer><Inner>123</Inner></Outer></Data></XML>';
SET @xml = @string;
SELECT @xml;


SELECT h.value('text()[1]', 'nvarchar(max)')
FROM @xml.nodes('XML') AS Statements(nodes)
OUTER APPLY @xml.nodes('Data/Outer/Inner') AS Data(h)

Запрос выбирает все узлы, то есть 123ABC, -123 и 123. Однако я хочу только выбрать 123ABC. Чтобы это работало с моим реальным примером, мне нужно изменить строку OUTER APPLY:

OUTER APPLY @xml.nodes('Data/Outer/Inner') AS Data(h)

Я почти уверен, что моя цель может быть достигнута путем применения фильтра следующим образом.

@xml.nodes('Data/Outer/Inner[FILTER]')

Однако я не смог найти ничего работающего.

ОБНОВЛЕНИЕ: Критерий фильтра можно сформулировать более подробно следующим образом. Должны быть показаны только те элементы, которые содержат хотя бы одну букву. Таким образом, следующие значения не должны отображаться, например: -1,5, 1,5, -33

Ответы [ 3 ]

1 голос
/ 12 марта 2020

Может быть, вы можете попробовать что-то вроде этого:

//Inner[not(.>0)]/text()

выводит "123AB C".

1 голос
/ 12 марта 2020

IF ваши XML данные на самом деле должны быть ниже (обратите внимание на конечный тег XML, который отсутствует в вашем примере):

<XML>
  <Data>
    <Outer>
      <Inner>123ABC</Inner>
    </Outer>
    <Outer>
      <Inner>123</Inner>
    </Outer>
  </Data>
</XML>

Тогда вы можете использовать выражение LIKE, чтобы проверить, что в нем есть символ, который не является числовым значением:

SELECT X.I.value('text()[1]','varchar(6)') AS [Inner]
FROM @xml.nodes('/XML/Data/Outer/Inner') AS X(I)
WHERE X.I.value('text()[1]','varchar(6)') LIKE '%[^0-9]%'

Если указан XML, который вы указали правильно, вы не сможете использовать XQUERY, так как он недействителен XML, поскольку узел XML не закрыт.

Edit: бит гипотезы, основанный на комментариях ( цель все еще немного неясна), однако, возможно:

SELECT X.I.value('text()[1]','varchar(6)') AS [Inner]
FROM @xml.nodes('/XML/Data/Outer/Inner') AS X(I)
WHERE TRY_CONVERT(decimal(38,0),X.I.value('text()[1]','varchar(6)')) IS NULL;

Или, основываясь на этом утверждении «Символ в виде буквы». , просто:

SELECT X.I.value('text()[1]','varchar(6)') AS [Inner]
FROM @xml.nodes('/XML/Data/Outer/Inner') AS X(I)
WHERE X.I.value('text()[1]','varchar(6)') LIKE '%[A-z]%';
0 голосов
/ 12 марта 2020

Это может быть так просто:

DECLARE @xml XML=
'<XML>
  <Data>
    <Outer>
      <Inner>123ABC</Inner>
    </Outer>
    <Outer>
      <Inner>-123</Inner>
    </Outer>
    <Outer>
      <Inner>-123 with blah</Inner>
    </Outer>
    <Outer>
      <Inner>1.23</Inner>
    </Outer>
    <Outer>
      <Inner>123</Inner>
    </Outer>
    <Outer>
      <Inner>-1.5</Inner>
    </Outer>
    <Outer>
      <Inner>-33</Inner>
    </Outer>
  </Data>
</XML>';

- запрос

SELECT numericInner.value('.','varchar(100)')
FROM @xml.nodes('/XML/Data/Outer/Inner[empty(. cast as xs:float?)]') A(numericInner);

Идея вкратце:

  • Мы используем .nodes() для извлечения производного набора <Inner> элементов
  • Но мы добавляем предикат : мы ищем только элементы, если их содержимое не может быть преобразовано в плавающее. В этом случае XQuery-приведение будет empty().

ОБНОВЛЕНИЕ

Благодаря подсказке @ lptr я хочу добавить, что значение 1.E+2 может нарушить это в очень особые ситуации (из-за научной нотации c). Но вы можете использовать приведение к decimal:

select cast('1.E+2' as xml).query('.[empty(. cast as xs:float?)]') AS CanBeCastedThereforeEmpty
      ,cast('1.E+2' as xml).query('.[empty(. cast as xs:decimal?)]') AS CannotBeCastedReturnedAsString
...