Выберите значения из поля XML в SQL Server 2008

112

Просто глядя на мое поле XML, мои строки выглядят так:

<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>

Обратите внимание, что это три строки в моей таблице.

Я хотел бы вернуть результат SQL в виде таблицы, как в

Jon  | Johnson
Kathy| Carter
Bob  | Burns

Какой запрос выполнит это?

Larsenal
источник
Нет возможности просто получить ВСЕ элементы в xml? У вас поочередно указывать? Это быстро становится утомительно. Вы можете сделать «выбрать * из таблицы», похоже, вы должны иметь возможность сделать «выбрать xml. * Из xml» без необходимости указывать каждый элемент, который вы хотите.
Кейт Тайлер

Ответы:

157

Учитывая, что поле XML называется xmlField ...

SELECT 
[xmlField].value('(/person//firstName/node())[1]', 'nvarchar(max)') as FirstName,
[xmlField].value('(/person//lastName/node())[1]', 'nvarchar(max)') as LastName
FROM [myTable]
Larsenal
источник
16
Вы должны использовать .nodes () и перекрестное применение, если xmlField содержит более одного элемента <person>.
Ремус Русану
SQL Server 2008 R2 Express вернул мне эту ошибку с вашим решением The XQuery syntax '/function()' is not supported.:; С другой стороны, @Remus Rusanu, кажется, делает это :)
RMiranda
2
Странно. За него проголосовали 102 раза, но этот ответ возвращает данные только из первой записи XML. И это относится к какой-то таблице [myTable] ... откуда это взялось ?!
Майк Гледхилл
Я пробовал это так много раз, но никогда не получалось. Мой XML - это <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>мой выбор select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). Я также пробовал выбрать e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)'), и '(//Type/node())[1]', '(./Type)[1]'и все другие комбинации, которые я мог придумать. Все, что я когда-либо получал, это NULL.
JonathanPeel
1
@MikeGledhill, он возвращает значения из нескольких записей XML, и мне это подходит. Кроме того, единственное имя таблицы, которую дает OP, - «моя таблица» :)
Пол
123

Учитывая, что данные XML поступают из таблицы 'table' и хранятся в столбце 'field': используйте методы XML , извлекайте значения с помощью xml.value(), узлы проекта с помощью xml.nodes(), используйте CROSS APPLYдля объединения:

SELECT 
    p.value('(./firstName)[1]', 'VARCHAR(8000)') AS firstName,
    p.value('(./lastName)[1]', 'VARCHAR(8000)') AS lastName
FROM table 
    CROSS APPLY field.nodes('/person') t(p)

Вы можете отказаться от nodes()и, cross applyесли каждое поле содержит ровно один элемент «человек». Если XML - это переменная, которую вы выбираете, FROM @variable.nodes(...)и вам не нужен cross apply.

Ремус Русану
источник
1
Интересно, насколько эффективен этот метод и есть ли способ лучше. Комбинация CROSS APPLY с результатами XPath может привести к довольно ресурсоемкому запросу.
redcalx
1
@thelocster: это не отличается от обычного доступа к данным. Методы повышения производительности XML хорошо документированы. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Ремус Русану
2
имейте в виду, что если в вашем XML определены пространства имен xmlns, вам необходимо определить их в выражении XQuery (XPath) выше. См. Пример на stackoverflow.com/a/1302150/656010 .
Tom Wayson
Немного отличается от того, что мне было нужно, но это было идеальное решение моей проблемы, которая заключалась в нескольких строках со столбцом XML - я хотел перебрать строки и вытащить поля данных из столбца XML и поместить их в оператор вставки. Итак, 5 строк, каждая для 3 столбцов данных в поле XML = 15 вставок, идеально.
Дэн Ричардсон
17

Этот пост был полезен для решения моей проблемы, которая имеет немного другой формат XML ... мой XML содержит список ключей, как в следующем примере, и я храню XML в столбце SourceKeys в таблице с именем DeleteBatch:

<k>1</k>
<k>2</k>
<k>3</k>

Создайте таблицу и заполните ее некоторыми данными:

CREATE TABLE dbo.DeleteBatch (
    ExecutionKey INT PRIMARY KEY,
    SourceKeys XML)

INSERT INTO dbo.DeleteBatch ( ExecutionKey, SourceKeys )
SELECT 1, 
    (CAST('<k>1</k><k>2</k><k>3</k>' AS XML))

INSERT INTO dbo.DeleteBatch ( ExecutionKey, SourceKeys )
SELECT 2, 
    (CAST('<k>100</k><k>101</k>' AS XML))

Вот мой SQL для выбора ключей из XML:

SELECT ExecutionKey, p.value('.', 'int') AS [Key]
FROM dbo.DeleteBatch
    CROSS APPLY SourceKeys.nodes('/k') t(p)

Вот результаты запроса ...

Ключ ExecutionKey
1 1
1 2
1 3
2 100
2 101
Монте
источник
9

Это может ответить на ваш вопрос:

select cast(xmlField as xml) xmlField into tmp from (
select '<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>' xmlField
union select '<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>'
union select '<person><firstName>Bob</firstName><lastName>Burns</lastName></person>'
) tb

SELECT
    xmlField.value('(person/firstName)[1]', 'nvarchar(max)') as FirstName
    ,xmlField.value('(person/lastName)[1]', 'nvarchar(max)') as LastName
FROM tmp

drop table tmp
Маркиньо Пели
источник
6

Вот это да. Это было действительно полезное обсуждение.

Некоторые из этих предложений все еще меня сбивают с толку. Всякий раз , когда я valueс [1]в строке, было бы только извлечет первое значение. И несколько советов, рекомендованных использовать cross applyкоторые (в моих тестах) просто вернули слишком много данных.

Итак, вот мой простой пример того, как вы создаете xmlобъект, а затем считываете его значения в таблицу.

DECLARE @str nvarchar(2000)

SET @str = ''
SET @str = @str + '<users>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Mike</firstName>'
SET @str = @str + '     <lastName>Gledhill</lastName>'
SET @str = @str + '     <age>31</age>'
SET @str = @str + '  </user>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Mark</firstName>'
SET @str = @str + '     <lastName>Stevens</lastName>'
SET @str = @str + '     <age>42</age>'
SET @str = @str + '  </user>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Sarah</firstName>'
SET @str = @str + '     <lastName>Brown</lastName>'
SET @str = @str + '     <age>23</age>'
SET @str = @str + '  </user>'
SET @str = @str + '</users>'

DECLARE @xml xml
SELECT @xml = CAST(CAST(@str AS VARBINARY(MAX)) AS XML) 

--  Iterate through each of the "users\user" records in our XML
SELECT 
    x.Rec.query('./firstName').value('.', 'nvarchar(2000)') AS 'FirstName',
    x.Rec.query('./lastName').value('.', 'nvarchar(2000)') AS 'LastName',
    x.Rec.query('./age').value('.', 'int') AS 'Age'
FROM @xml.nodes('/users/user') as x(Rec)

И вот результат:

введите описание изображения здесь

Это странный синтаксис, но на достойном примере его достаточно легко добавить к вашим собственным функциям SQL Server.

Кстати, вот правильный ответ на этот вопрос.

Предполагая, что у вас есть XML-данные в @xmlпеременной типа xml(как показано в моем примере выше), вот как вы вернете три строки данных из xml, указанных в вопросе:

SELECT 
    x.Rec.query('./firstName').value('.', 'nvarchar(2000)') AS 'FirstName',
    x.Rec.query('./lastName').value('.', 'nvarchar(2000)') AS 'LastName'
FROM @xml.nodes('/person') as x(Rec)

введите описание изображения здесь

Майк Гледхилл
источник
Я не понимаю, насколько это правильный ответ. OP запрашивает запрос столбца из таблицы типа XML, и в этом случае вам нужно либо использовать [1]порядковый номер индекса, чтобы заставить его возвращать 1 строку, либо вам нужно перекрестно применить столбец, nodes()чтобы получить структура, против которой может работать xpath. Ваш код не соответствует этому сценарию без множества изменений. Вы используете переменную, а не столбец таблицы. Вы также злоупотребляете query()функцией, которая возвращает xml. например, вы могли бы простоx.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Давос
3

Если вы можете заключить свой XML в корневой элемент - скажем, ваше решение - следующее:

DECLARE @PersonsXml XML = '<persons><person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person></persons>'

SELECT  b.value('(./firstName/text())[1]','nvarchar(max)') as FirstName, b.value('(./lastName/text())[1]','nvarchar(max)') as LastName
FROM @PersonsXml.nodes('/persons/person') AS a(b)

введите описание изображения здесь

Моиз Танкивала
источник
3

MSSQL использует обычные правила XPath следующим образом:

  • nodename Выбирает все узлы с именем "nodename"
  • / Выбирает из корневого узла
  • // Выбирает узлы в документе из текущего узла, которые соответствуют выделению, независимо от того, где они находятся
  • . Выбирает текущий узел
  • .. Выбирает родительский элемент текущего узла
  • @ Выбирает атрибуты

W3Schools

Артур
источник
2
SELECT 
cast(xmlField as xml).value('(/person//firstName/node())[1]', 'nvarchar(max)') as FirstName,
cast(xmlField as xml).value('(/person//lastName/node())[1]', 'nvarchar(max)') as LastName
FROM [myTable]
шахир
источник
0

/ * В этом примере используется переменная XML со схемой * /

IF EXISTS (SELECT * FROM sys.xml_schema_collections 
           WHERE name = 'OrderingAfternoonTea')
BEGIN
    DROP XML SCHEMA COLLECTION dbo.OrderingAfternoonTea 
END
GO

CREATE XML SCHEMA COLLECTION dbo.OrderingAfternoonTea AS
N'<?xml version="1.0" encoding="UTF-16" ?>
  <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     targetNamespace="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     xmlns="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     xmlns:TFor2="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     elementFormDefault="qualified"
     version="0.10"
   > 
    <xsd:complexType name="AfternoonTeaOrderType">
       <xsd:sequence>
         <xsd:element name="potsOfTea" type="xsd:int"/>
         <xsd:element name="cakes" type="xsd:int"/>
         <xsd:element name="fruitedSconesWithCream" type="xsd:int"/>
         <xsd:element name="jams" type="xsd:string"/>
      </xsd:sequence>
      <xsd:attribute name="schemaVersion" type="xsd:long" use="required"/>
    </xsd:complexType>

    <xsd:element name="afternoonTeaOrder"
                 type="TFor2:AfternoonTeaOrderType"/>

  </xsd:schema>' ;
GO

DECLARE @potsOfTea int;
DECLARE @cakes int;
DECLARE @fruitedSconesWithCream int;
DECLARE @jams nvarchar(128);

DECLARE @RequestMsg NVARCHAR(2048);
DECLARE @RequestXml XML(dbo.OrderingAfternoonTea);

set @potsOfTea = 5;
set @cakes = 7;
set @fruitedSconesWithCream = 25;
set @jams = N'medlar jelly, quince and mulberry';

SELECT @RequestMsg = N'<?xml version="1.0" encoding="utf-16" ?>
<TFor2:afternoonTeaOrder schemaVersion="10"
    xmlns:TFor2="http://Tfor2.com/schemas/actions/orderAfternoonTea">
    <TFor2:potsOfTea>' + CAST(@potsOfTea as NVARCHAR(20)) 
        + '</TFor2:potsOfTea>
    <TFor2:cakes>' + CAST(@cakes as NVARCHAR(20)) + '</TFor2:cakes>
    <TFor2:fruitedSconesWithCream>' 
        + CAST(@fruitedSconesWithCream as NVARCHAR(20))
        + '</TFor2:fruitedSconesWithCream>
    <TFor2:jams>' + @jams + '</TFor2:jams>
</TFor2:afternoonTeaOrder>';

SELECT @RequestXml  = CAST(CAST(@RequestMsg AS VARBINARY(MAX)) AS XML) ;

with xmlnamespaces('http://Tfor2.com/schemas/actions/orderAfternoonTea'
                    as tea)
select
    cast( x.Rec.value('.[1]/@schemaVersion','nvarchar(20)') as bigint )
        as schemaVersion,
    cast( x.Rec.query('./tea:potsOfTea')
               .value('.','nvarchar(20)') as bigint ) as potsOfTea,
    cast( x.Rec.query('./tea:cakes')
               .value('.','nvarchar(20)') as bigint )  as cakes,
    cast( x.Rec.query('./tea:fruitedSconesWithCream')
               .value('.','nvarchar(20)') as bigint ) 
      as fruitedSconesWithCream,
    x.Rec.query('./tea:jams').value('.','nvarchar(50)')  as jams
from @RequestXml.nodes('/tea:afternoonTeaOrder')  as x(Rec);

select @RequestXml.query('/*')
Жилет с набивным рисунком
источник