SQLXML - элемент узла поиска и запроса? - PullRequest
1 голос
/ 02 августа 2011

У меня есть XML, подобный этому, хранящийся в столбце типа данных XML (будет иметь несколько таких строк в таблице) -

<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Elem1 type="T1">
    <Name type="string" display="First name">John</Name>
    <TimeZone display="Time zone">
      <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
    </TimeZone>
  </Elem1>
</Root> 

Как я могу выполнить фильтрацию по элементу узла (используя SQL SERVER 2008 R2) - получить все узлы 'Elem1' или получить все узлы 'Name' или получить все узлы TimeZone?Что-то вроде использования функции local-name ()?

EDIT - Решение для части -

Я получил решение частично (см. Ответ Джона ниже изатем выполните это) -

SELECT C1.query('fn:local-name(.)') AS Nodes FROM [dbo].[MyXmlTable] AS MyXML CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T ( C1 ) 

Приведенный выше запрос возвращает все элементы узла в таблице TABLE.Теперь я хочу сказать фильтр по конкретным элементам и вернуть элемент и его значение или значение его атрибута.Как этого добиться (с помощью предложения WHERE или любого другого механизма фильтрации)?

Ответы [ 3 ]

1 голос
/ 09 августа 2011

Я не уверен, какой результат вы ищете, но что-то вроде этого возможно.

declare @T table(XMLCol xml)
insert into @T values
('<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Elem1 type="T1">
    <Name type="string" display="First name">John</Name>
    <TimeZone display="Time zone">
      <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
    </TimeZone>
  </Elem1>
</Root>') 

declare @Node varchar(50)
set @Node = 'Elem1'

select N.query('.') as Value
from @T as T
  cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("@Node")]') as X(N)

Результат:

<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1">
  <p1:Name type="string" display="First name">John</p1:Name>
  <p1:TimeZone display="Time zone">
    <p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName>
  </p1:TimeZone>
</p1:Elem1>

Редактировать

Если вы хотите получить действительное значение вместо всего XML, вы можете сделать это следующим образом.

declare @Node varchar(50)
set @Node = 'TimeZone'

select N.value('.', 'varchar(100)') as Value
from @T as T
  cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("@Node")]') as X(N)

Результат:

Value
------------------
GMT Standard Time
0 голосов
/ 10 августа 2011

Вы можете преобразовать XML в таблицу, как здесь:

  declare @XML xml='<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Elem1 type="T1">
    <Name type="string" display="First name">John</Name>
    <TimeZone display="Time zone">
      <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
    </TimeZone>
  </Elem1>
</Root> '

;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org'),
numbers as(
SELECT  ROW_NUMBER() OVER(ORDER BY o1.object_id,o2.object_id) Num
FROM    sys.objects o1 CROSS JOIN  sys.objects o2),
 c as(
SELECT 
b.value('local-name(.)','nvarchar(1000)') Node_Name,
b.value('./text()[1]','nvarchar(1000)') Node_Value,
b.value('count(@*)','nvarchar(MAX)') AttributeCount,
Num Attribute_Number
FROM 
@xml.nodes('Root//*') a(b)
CROSS APPLY Numbers
WHERE Num<=b.value('count(@*)','nvarchar(MAX)')
)
SELECT c.Node_Name,c.node_Value,Attribute_Number,
    @XML.query('for $Attr in //*/.[local-name(.)=sql:column("Node_Name")]/@*[sql:column("Attribute_Number")] return local-name($Attr)').value('.','nvarchar(MAX)') Attribute_Name,
    @XML.value('data(//*/.[local-name(.)=sql:column("Node_Name")]/@*[sql:column("Attribute_Number")])[1]','nvarchar(1000)') Attribute_Value
FROM    c

Результат:

Node_Name   node_Value          Attribute_Number    Attribute_Name  Attribute_Value
Elem1        NULL                       1              type             T1
Name         John                       1              type           string
Name         John                       2              display        First name
TimeZone     NULL                       1              display        Time zone
DisplayName GMT Standard Time           1              type            string
DisplayName GMT Standard Time           2              display     Display name

Позже вы можете запросить этот результат, чтобы получить значение узла / атрибутачто вам нужно.

Но это работает только в вашем примере, когда у вас есть только один узел и все имена уникальны.В многоузловом XML вы должны использовать иерархическую нумерацию типа «1-1-2» или что-то вроде этого.Это намного сложнее, и я не предлагаю идти по этому пути.

0 голосов
/ 02 августа 2011

Мне не совсем понятно, как должен выглядеть ваш вывод.Однако, это должно помочь вам начать:

create table MyXmlTable (MyXmlCol xml)
insert into MyXmlTable (MyXmlCol) values 
(
'
<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Elem1 type="T1">
    <Name type="string" display="First name">John</Name>
    <TimeZone display="Time zone">
      <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
    </TimeZone>
  </Elem1>
  <Elem1 type="T2">
    <Name type="string" display="First name">Fred</Name>
    <TimeZone display="Time zone">
      <DisplayName type="string" display="Display name">EST Standard Time</DisplayName>
    </TimeZone>
  </Elem1>
</Root> 
');

;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org')
select MyXmlCol.query('/Root/Elem1/Name')
from MyXmlTable

Это запрашивает XML для элементов «Имя» - вы можете изменить запрос в зависимости от того, какой именно вывод вы хотите.Это немного длинно, но статья MSDN о SQLXML довольно информативна:

http://msdn.microsoft.com/en-us/library/ms345117(v=sql.90).aspx

Надеюсь, это поможет!

Джон

Обновление: вы можете добавить кое-что из предложения whereкак это.Я до сих пор не понимаю, как вы хотите, чтобы выходные данные выглядели, но это отфильтровывает значения "Elem1":

SELECT C1.query('fn:local-name(.)') AS Nodes 
FROM [dbo].[MyXmlTable] AS MyXML 
CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T ( C1 ) 
WHERE CAST(C1.query('fn:local-name(.)') AS NVARCHAR(32)) <> 'Elem1'

Еще одно обновление;надеюсь, это ответ, который вы ищете!

Попробуйте использовать подстановочный знак в запросе.Мне пришлось использовать динамический SQL, потому что функция XML query () будет принимать только строковые литералы для путей (вы можете использовать sql: variable ("@ filter") для значений, но я не смог заставить это работать для пути.)

DECLARE @filter nvarchar(20)
SET @filter = '*/Elem1'

DECLARE @sqlCommand nvarchar(1000)
SET @sqlCommand = 
    ';WITH XMLNAMESPACES(DEFAULT ''http://tempuri.org'')
    select MyXmlCol.query(''' + @filter + ''')
    from MyXmlTable'
print @sqlCommand
EXECUTE sp_executesql @sqlCommand, N'@filter nvarchar(20)', @filter = @filter

Это вернет XML Elem1 (и все подузлы):

<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1">
  <p1:Name type="string" display="First name">John</p1:Name>
  <p1:TimeZone display="Time zone">
    <p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName>
  </p1:TimeZone>
</p1:Elem1>
<p2:Elem1 xmlns:p2="http://tempuri.org" type="T2">
  <p2:Name type="string" display="First name">Fred</p2:Name>
  <p2:TimeZone display="Time zone">
    <p2:DisplayName type="string" display="Display name">EST Standard Time</p2:DisplayName>
  </p2:TimeZone>
</p2:Elem1>

И если вы хотите выбрать «TimeZone», вы сделаете это:

SET @filter = '*/*/TimeZone'
...