SQL Server 2008'de XML alanından değerleri seçin


112

XML alanıma baktığımda satırlarım şöyle görünüyor:

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

Bunların tablomdaki üç satır olduğunu unutmayın.

Bir SQL sonucunu aşağıdaki gibi bir tablo olarak döndürmek istiyorum

Jon  | Johnson
Kathy| Carter
Bob  | Burns

Bunu hangi sorgu gerçekleştirecek?


Xml'deki TÜM öğeleri almanın bir yolu yok mu? Tek tek belirtmek zorunda mısın? Bu gerçekten çok sıkıcı oluyor. "Tablodan * seç" yapabilirsiniz, görünüşe göre istediğiniz her bir öğeyi belirtmek zorunda kalmadan "xml'den xml. * Seç" yapabilmelisiniz.
Keith Tyler

Yanıtlar:


157

XML alanının 'xmlField' olarak adlandırıldığı göz önüne alındığında ...

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

16
XmlField birden fazla <person> öğesi içeriyorsa .nodes () kullanmanız ve çapraz uygulamanız gerekir.
Remus Rusanu

SQL Server 2008 R2 Express, çözümünüzle birlikte bana şu hatayı döndürdü The XQuery syntax '/function()' is not supported.:; Öte yandan @Remus Rusanu bunu yapıyor gibi görünüyor :)
RMiranda

2
Tuhaf. Bu, 102 kez oylandı, ancak bu yanıt yalnızca ilk XML kaydından veri döndürür . Ve bazı [myTable] tablolarına atıfta bulunuyor ... bu nereden geldi?!
Mike Gledhill

Bunu birçok kez denedim ve hiç çalışmadım. XML'im <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>, benim seçimim select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). Ayrıca seçme denedim e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)')ve '(//Type/node())[1]', '(./Type)[1]'ve diğer her kombinasyon ben düşünebiliriz. Tek aldığım şey NULL.
JonathanPeel

1
@MikeGledhill benim için çok sayıda XML kaydından gelen değerleri döndürür. Ayrıca OP'nin verdiği tablonun tek adı "masam" :)
Paul

123

XML verilerinin bir tablo 'tablodan' geldiğini ve bir 'alan' sütununda depolandığını göz önünde bulundurarak: XML yöntemlerini kullanın, değerleri çıkarın xml.value(), ile proje düğümlerini xml.nodes()kullanın CROSS APPLY, birleştirmek için kullanın :

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)

Sen hendek nodes()ve cross applyher alan tam olarak bir unsuru 'kişi' içeriyorsa. XML seçtiğiniz bir değişkendiyse FROM @variable.nodes(...)ve cross apply.


1
Bu yöntemin ne kadar etkili olduğunu ve daha iyi bir yolu olup olmadığını merak ediyorum. XPath sonuçlarıyla CROSS APPLY kombinasyonu, oldukça kaynak aç bir sorguya neden olabilir gibi görünüyor.
redcalx

1
@thelocster: Bu, sıradan veri erişiminden farklı değildir. XML performansını iyileştirme teknikleri iyi bir şekilde belgelenmiştir. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Remus Rusanu

2
XML'inizde tanımlanmış xmlns ad alanları varsa, bunları yukarıdaki XQuery (XPath) ifadesinde tanımlamanız gerekeceğini unutmayın. Örnek için stackoverflow.com/a/1302150/656010 sayfasına bakın .
Tom Wayson

İhtiyaç duyduğum şeyden biraz farklı, ancak bu, bir XML sütunu ile birden çok satır olan bir soruna mükemmel bir çözümdü - satırlar arasında döngü yapmak ve veri alanlarını XML sütunundan çıkarmak ve bunları içine koymak istedim bir ekleme ifadesi. Yani 5 satır, her biri XML alanında 3 sütun veri için = 15 ekleme, mükemmel.
dan richardson

17

Bu gönderi, biraz farklı XML biçimine sahip sorunumu çözmemde yardımcı oldu ... XML'im aşağıdaki örnekteki gibi bir anahtar listesi içeriyor ve XML'i DeleteBatch adlı bir tabloda SourceKeys sütununda saklıyorum:

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

Tabloyu oluşturun ve bazı verilerle doldurun:

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

XML’den anahtarları seçmek için benim SQL’im:

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

İşte sorgu sonuçları ...

Yürütme Anahtarı Anahtarı
1 1
1 2
1 3
2 100
2 101

9

Bu, sorunuza cevap verebilir:

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

Blimey. Bu, keşfedilmesi gerçekten yararlı bir konu oldu.

Hala bu önerilerden bazılarını kafa karıştırıcı buldum. Ben ne zaman kullanılırsa valueile [1]dizede, yalnızca ilk değer alınır olacaktır. Ve cross apply(benim testlerimde) çok fazla veri geri getiren bazı öneriler kullanılması önerilir .

İşte bir xmlnesneyi nasıl yaratacağınıza ve ardından değerlerini bir tablo halinde okuyacağınıza dair basit bir örnek .

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)

Ve işte çıktı:

görüntü açıklamasını buraya girin

Tuhaf bir sözdizimi, ancak iyi bir örnekle, kendi SQL Server işlevlerinize eklemek yeterince kolaydır.

Bundan bahsetmişken, işte bu sorunun doğru cevabı.

Xml verilerinizin bir tür @xmldeğişkeninde olduğunu varsayarsak xml(yukarıdaki örneğimde gösterildiği gibi), soruda alıntılanan xml'den üç satır veriyi şu şekilde döndürebilirsiniz:

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)

görüntü açıklamasını buraya girin


Bunun nasıl doğru cevap olduğunu anlamıyorum. OP XML türünde bir tablodan bir sütun sorgulayarak istiyor ve siz de kullanımına sahip olduğu durumda [1]ile sütun uygulamak, endeks sıra zorlamak için 1 satır dönmek veya geçmek zorunda nodes()bir olsun xpath'in ona karşı çalışmasını sağlayabilecek yapı. Kodunuz, çok fazla değişiklik yapılmadan bu senaryoya çevrilmez. Tablo sütunu değil, değişken kullanıyorsunuz. Ayrıca query()xml döndüren işlevi aşırı kullanıyorsunuz . örneğin, sadece yapabilirdinx.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Davos

3

XML'nizi bir kök öğeye sarabiliyorsanız - diyelim ki çözümünüz şudur:

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)

görüntü açıklamasını buraya girin


3

MSSQL, aşağıdaki gibi normal XPath kurallarını kullanır:

  • nodename "nodename" adlı tüm düğümleri seçer
  • / Kök düğümden seçer
  • // Belgedeki, nerede olurlarsa olsunlar seçimle eşleşen geçerli düğümden düğümleri seçer
  • . Mevcut düğümü seçer
  • .. Geçerli düğümün üst öğesini seçer
  • @ Öznitelikleri seçer

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

/ * Bu örnek, şemalı bir XML değişkeni kullanıyor * /

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('/*')
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.