Виберіть значення з поля 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

Який запит буде це виконувати?


Немає способу просто ВСІ елементи отримати у xml? Ви повинні вказати по черзі? Це стає дуже нудно швидко. Ви можете зробити "select * from table", схоже, ви повинні мати можливість "select xml. * From 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]

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. Також єдина назва таблиці, яку дає ОП, - "мій стіл" :)
Пол,

123

Зважаючи на те, що дані XML надходять із таблиці «таблиця» і зберігаються у колонці «поле»: використовуйте методи 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якщо кожне поле містить точно один елемент 'person'. Якщо обрана вами змінна 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
Remus Rusanu

2
майте на увазі, що якщо у вашому XML визначено простори імен xmlns, вам потрібно буде визначити їх у виразі XQuery (XPath) вище. Для прикладу див. Stackoverflow.com/a/1302150/656010 .
Том Уейсон

Трохи відмінна від того, що мені було потрібно, але це було ідеальним рішенням проблеми, з якою у мене виникли кілька рядків із стовпцем 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 Key
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)

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


Я не бачу, як це правильна відповідь. ОП запитує запит на стовпчик із таблиці, що має тип 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('/*')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.