เลือกค่าจากฟิลด์ 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 ได้หรือไม่? คุณต้องระบุทีละรายการ? มันน่าเบื่ออย่างรวดเร็ว คุณสามารถ "เลือก * จากตาราง" ดูเหมือนว่าคุณควรจะสามารถ "เลือก xml. * จาก xml" ได้โดยไม่ต้องระบุทุกองค์ประกอบที่คุณต้องการ
Keith Tyler

คำตอบ:


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
คุณต้องใช้. โหนด () และใช้ข้ามหาก xmlField มีองค์ประกอบ <person> มากกว่าหนึ่งรายการ
Remus Rusanu

SQL Server 2008 R2 Express ส่งคืนข้อผิดพลาดนี้ให้ฉันพร้อมกับโซลูชันของคุณ: The XQuery syntax '/function()' is not supported.; ในทางกลับกัน @Remus Rusanu ดูเหมือนจะทำ :)
RMiranda

2
แปลกประหลาด. สิ่งนี้ได้รับการโหวตถึง 102 ครั้ง แต่คำตอบนี้ส่งคืนเฉพาะข้อมูลจากระเบียน XML แรกเท่านั้น และมันหมายถึงตาราง [myTable] ... มันมาจากไหน?!
Mike Gledhill

ฉันได้ลองหลายครั้งแล้วและไม่เคยได้ผล 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 ให้คือ "โต๊ะของฉัน" :)
Paul

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ถ้าแต่ละฟิลด์มีองค์ประกอบ 'คน' เพียงอย่างเดียว หาก 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สำหรับตัวอย่าง
Tom Wayson

แตกต่างจากสิ่งที่ฉันต้องการเล็กน้อย แต่นี่เป็นวิธีแก้ปัญหาที่สมบูรณ์แบบสำหรับปัญหาที่ฉันพบซึ่งมีหลายแถวที่มีคอลัมน์ XML - ฉันต้องการวนรอบแถวและดึงฟิลด์ข้อมูลจากภายในคอลัมน์ XML และใส่ไว้ใน คำสั่งแทรก ดังนั้น 5 แถวแต่ละคอลัมน์สำหรับข้อมูล 3 คอลัมน์ในฟิลด์ XML = 15 แทรกสมบูรณ์แบบ
dan richardson

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

Blimey. นี่เป็นชุดข้อความที่มีประโยชน์มากในการค้นพบ

ฉันยังพบว่าคำแนะนำเหล่านี้สับสน เมื่อใดก็ตามที่ผมใช้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()เพื่อให้ได้ a โครงสร้างที่สามารถให้ 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('/*')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.