Проблема с использованием переменной атрибута в T- SQL XML value () - PullRequest
0 голосов
/ 17 июня 2020
DECLARE @NodePath VARCHAR(20) = 'C',
        @NodeVariable VARCHAR(20) = '@name',
        @result XML;
SET @result = '
<A>
    <B>
        <C name="Name01"/>
    </B>
    <B>
        <C name = "Name02"/>
    </B>
</A>
'
SELECT T.c.value('sql:variable("@NodeVariable")', 'VARCHAR(20)')
FROM @result.nodes('//*[local-name()=sql:variable("@NodePath")]') T(c)

Я хочу получить атрибуты XML, например:

|Name|
|Name01|
|Name02|

Но результат:

|Name|
|@name|
|@name|

Как решить эту проблему? Поскольку я хочу создать функцию, которая принимает в качестве аргументов путь и имя атрибута, функция OPENXML не разрешена.

Ответы [ 2 ]

2 голосов
/ 17 июня 2020

Может быть немного проще:

SELECT x.n.value('.', 'nvarchar(20)') as 'Name'
FROM @result.nodes('/A
                    /B
                    /*[local-name() =sql:variable("@NodePath")]
                    /@*[local-name()=sql:variable("@NodeVariable")]') x(n)

Вкратце идея:

  • Погрузитесь ниже <B> (или используйте глубокий поиск с //, если вы можете быть уверены, что <C> не будет в любом другом месте)
  • Найдите любой элемент с заданным именем
  • выберите атрибут с заданным именем (атрибуты являются одноэлементными для каждого элемента по определению)
  • используйте value() на текущем узле , чтобы вернуть содержимое.

Что может мешать этому: множественные случаи из <C> ниже <B>

ОБНОВЛЕНИЕ Некоторые дополнения к XPath и local-name()

Просто попробуйте следующее:

declare @result xml =
N'<A>
    <B>
        <C name="Name01"/>
    </B>
    <TheSecondInA />
    <B>
        <C name = "Name02"/>
    </B>
    <OneMore someAttr="x" oneMoreAttr="y" theLastAttr="z" >SomeText</OneMore>
</A>';

SELECT @result.value('local-name((//TheSecondInA)[1])','varchar(100)')
      ,@result.value('local-name((/A/*[2])[1])','varchar(100)')
      ,@result.value('local-name(/A[1]/*[2])','varchar(100)')
      ,@result.value('local-name((//*[@someAttr]/@*[2])[1])','varchar(100)')
      ,@result.value('local-name((/A/OneMore/@*[3])[1])','varchar(100)')
      ,@result.value('local-name((/A/OneMore/@*[last()])[1])','varchar(100)')

      ,@result.value('local-name((/A/OneMore/text())[1])','varchar(100)')
      ,@result.value('local-name((/DoesNotExist)[1])','varchar(100)')

Как видите, функция local-name() должен получить singleton XPath .

  • глубокий поиск переходит к первому появлению именованного узла
  • То же самое возвращается вторым элементом ниже <A>
  • Нам этот (SomeXpath)[1] не нужен, если сам путь гарантирует возврат синглтона.
  • Точно так же мы можем выбрать третий атрибут на заданном пути
  • Чтобы получить самый последний атрибут (или элемент), мы можем использовать last()
  • Если текущий узел является узлом text() или если элемент не существует, мы возвращаем пустую строку.

Подсказка: с похожими выражениями XPath вы можете использовать .value() для получения локального содержимого, .exist() для проверки наличия (или его отсутствия) и изменения заданного местоположения ...

2 голосов
/ 17 июня 2020

В функции value() вам необходимо использовать синтаксис @*[local-name()=...], например:

declare @NodePath nvarchar(20) = 'C';
declare @NodeVariable nvarchar(20) = 'name';
declare @result xml =
N'<A>
    <B>
        <C name="Name01"/>
    </B>
    <B>
        <C name = "Name02"/>
    </B>
</A>'
select x.n.value('(@*[local-name()=sql:variable("@NodeVariable")])[1]', 'nvarchar(20)') as 'Name'
from @result.nodes('//*[local-name()=sql:variable("@NodePath")]') x(n)

Что дает:

Name
------
Name01
Name02
...