Pilih nilai dari bidang XML di SQL Server 2008


112

Hanya melihat bidang XML saya, baris saya terlihat seperti ini:

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

Perhatikan bahwa ini adalah tiga baris di tabel saya.

Saya ingin mengembalikan hasil SQL sebagai tabel seperti di

Jon  | Johnson
Kathy| Carter
Bob  | Burns

Pertanyaan apa yang akan menyelesaikannya?


Apakah tidak ada cara untuk mendapatkan SEMUA elemen di xml? Anda harus menentukan satu per satu? Itu menjadi sangat membosankan dengan cepat. Anda dapat melakukan "pilih * dari tabel", sepertinya Anda dapat melakukan "pilih xml. * Dari xml" tanpa harus menentukan setiap elemen yang Anda inginkan.
Keith Tyler

Jawaban:


157

Mengingat bidang XML bernama '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
Anda harus menggunakan .nodes () dan penerapan silang jika xmlField berisi lebih dari satu elemen <person>.
Remus Rusanu

SQL Server 2008 R2 Express, kembali saya kesalahan ini dengan solusi Anda: The XQuery syntax '/function()' is not supported.; Di sisi lain @Remus Rusanu tampaknya melakukannya :)
RMiranda

2
Aneh. Ini telah dipilih 102 kali, tetapi jawaban ini hanya mengembalikan data dari catatan XML pertama . Dan itu mengacu pada beberapa tabel [myTable] ... dari mana asalnya?!
Mike Gledhill

Saya telah mencoba ini berkali-kali dan tidak pernah berhasil. XML <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>saya, pilih saya select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). Saya juga mencoba pilih e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)'), dan '(//Type/node())[1]', '(./Type)[1]', dan setiap kombinasi lain saya bisa memikirkan. Yang saya dapatkan hanyalah NULL.
JonathanPeel

1
@MikeGledhill mengembalikan nilai dari beberapa catatan XML baik-baik saja bagi saya. Juga satu-satunya nama untuk tabel yang diberikan OP adalah "meja saya" :)
Paul

123

Mempertimbangkan bahwa data XML berasal dari tabel 'tabel' dan disimpan di kolom 'bidang': gunakan metode XML , ekstrak nilai dengan xml.value(), simpul proyek dengan xml.nodes(), gunakan CROSS APPLYuntuk bergabung:

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)

Anda dapat membuang nodes()dan cross applyjika setiap bidang berisi tepat satu elemen 'orang'. Jika XML adalah variabel yang Anda pilih FROM @variable.nodes(...)dan Anda tidak memerlukan cross apply.


1
Saya bertanya-tanya seberapa efisien metode ini dan apakah ada cara yang lebih baik. Hasil kombinasi CROSS APPLY dengan XPath sepertinya akan menghasilkan kueri yang membutuhkan banyak sumber daya.
redcalx

1
@thelocster: ini tidak berbeda dengan akses data biasa. Teknik untuk meningkatkan kinerja XML didokumentasikan dengan baik. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Remus Rusanu

2
perlu diingat bahwa jika XML Anda memiliki ruang nama xmlns yang ditentukan, Anda harus menentukannya dalam ekspresi XQuery (XPath) di atas. Lihat stackoverflow.com/a/1302150/656010 sebagai contoh.
Tom Wayson

Sedikit berbeda dengan apa yang saya butuhkan, tetapi ini adalah solusi sempurna untuk masalah yang saya alami yaitu beberapa baris dengan kolom XML - Saya ingin memutar baris dan menarik bidang data dari dalam kolom XML dan memasukkannya ke dalamnya pernyataan masukkan. Jadi 5 baris, masing-masing untuk 3 kolom data di bidang XML = 15 sisipan, sempurna.
dan richards

17

Posting ini sangat membantu untuk menyelesaikan masalah saya yang memiliki format XML yang sedikit berbeda ... XML saya berisi daftar kunci seperti contoh berikut dan saya menyimpan XML di kolom SourceKeys dalam tabel bernama DeleteBatch:

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

Buat tabel dan isi dengan beberapa data:

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))

Inilah SQL saya untuk memilih kunci dari XML:

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

Berikut hasil query ...

ExecutionKey Key
1 1
1 2
1 3
2 100
2 101

9

Ini mungkin menjawab pertanyaan Anda:

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

Astaga. Ini adalah utas yang sangat berguna untuk ditemukan.

Saya masih menemukan beberapa dari saran ini membingungkan. Setiap kali saya menggunakan valuedengan [1]dalam string, itu hanya akan mengambil nilai pertama. Dan beberapa saran merekomendasikan penggunaan cross applyyang (dalam pengujian saya) hanya menghasilkan terlalu banyak data.

Jadi, inilah contoh sederhana saya tentang cara membuat xmlobjek, lalu membacakan nilainya ke dalam tabel.

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)

Dan inilah hasilnya:

masukkan deskripsi gambar di sini

Ini sintaks yang aneh, tetapi dengan contoh yang layak, cukup mudah untuk menambahkan fungsi SQL Server Anda sendiri.

Ngomong-ngomong, inilah jawaban yang benar untuk pertanyaan ini.

Dengan asumsi Anda memiliki data xml Anda dalam @xmlvariabel tipe xml(seperti yang ditunjukkan dalam contoh saya di atas), berikut ini cara Anda mengembalikan tiga baris data dari xml yang dikutip dalam pertanyaan:

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)

masukkan deskripsi gambar di sini


Saya tidak melihat bagaimana ini adalah jawaban yang benar. OP meminta untuk menanyakan kolom dari tabel yang berjenis XML, dan dalam hal ini Anda harus menggunakan [1], indeks ordinal untuk memaksanya mengembalikan 1 baris, atau Anda harus menerapkan silang kolom dengan nodes()untuk mendapatkan a struktur yang dapat dijalankan oleh xpath terhadapnya. Kode Anda tidak diterjemahkan ke skenario itu tanpa banyak modifikasi. Anda menggunakan variabel, bukan kolom tabel. Anda juga terlalu banyak menggunakan query()fungsi yang mengembalikan xml. misalnya Anda bisa sajax.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Davos

3

Jika Anda dapat membungkus XML Anda dalam elemen root - katakan berikut ini adalah solusi Anda:

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)

masukkan deskripsi gambar di sini


3

MSSQL menggunakan aturan XPath biasa sebagai berikut:

  • nodename Memilih semua node dengan nama "nodename"
  • / Memilih dari root node
  • // Memilih node dalam dokumen dari node saat ini yang cocok dengan pilihan di mana pun mereka berada
  • . Memilih node saat ini
  • .. Memilih induk dari node saat ini
  • @ Memilih atribut

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

/ * Contoh ini menggunakan variabel XML dengan skema * /

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('/*')
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.