Chọn giá trị từ trường XML trong SQL Server 2008


112

Chỉ cần nhìn vào trường XML của tôi, các hàng của tôi trông như thế này:

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

Lưu ý rằng đây là ba hàng trong bảng của tôi.

Tôi muốn trả về một kết quả SQL dưới dạng một bảng như trong

Jon  | Johnson
Kathy| Carter
Bob  | Burns

Truy vấn nào sẽ thực hiện được điều này?


Không có cách nào để chỉ lấy TẤT CẢ các phần tử trong xml? Bạn phải chỉ định từng cái một? Điều đó thực sự tẻ nhạt nhanh chóng. Bạn có thể thực hiện "select * from table", có vẻ như bạn sẽ có thể thực hiện "select xml. * From xml" mà không cần phải chỉ định từng phần tử bạn muốn.
Keith Tyler

Câu trả lời:


157

Cho rằng trường XML được đặt tên là '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
Bạn phải sử dụng .nodes () và áp dụng chéo nếu xmlField chứa nhiều hơn một phần tử <person>.
Remus Rusanu

SQL Server 2008 R2 Express, quay trở lại tôi lỗi này với giải pháp của bạn: The XQuery syntax '/function()' is not supported.; Mặt khác, @Remus Rusanu dường như làm được điều đó :)
RMiranda

2
Kỳ quái. Câu trả lời này đã được bình chọn 102 lần, nhưng câu trả lời này chỉ trả về dữ liệu từ bản ghi XML đầu tiên . Và nó đề cập đến một số bảng [myTable] ... điều đó đến từ đâu ?!
Mike Gledhill

Tôi đã thử điều này rất nhiều lần và chưa bao giờ nó hoạt động. XML của tôi là <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>, lựa chọn của tôi là select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). Tôi cũng đã cố gắng chọn e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)'), và '(//Type/node())[1]', '(./Type)[1]'và mọi sự kết hợp khác mà tôi có thể nghĩ đến. Tất cả những gì tôi nhận được là NULL.
JonathanPeel

1
@MikeGledhill nó trả về giá trị từ nhiều bản ghi XML tốt cho tôi. Ngoài ra, tên duy nhất của bảng mà OP cung cấp là "bàn của tôi" :)
Paul

123

Xem xét rằng dữ liệu XML đến từ một bảng 'bảng' và được lưu trữ trong một 'trường' cột: hãy sử dụng phương thức XML , trích xuất các giá trị với xml.value(), các nút dự án với xml.nodes(), sử dụng CROSS APPLYđể nối:

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)

Bạn có thể bỏ qua nodes()cross applynếu mỗi trường chứa chính xác một phần tử 'người'. Nếu XML là một biến bạn chọn FROM @variable.nodes(...)và bạn không cần cross apply.


1
Tôi tự hỏi phương pháp này hiệu quả như thế nào và liệu có cách nào tốt hơn không. Kết hợp ÁP DỤNG CROSS với kết quả XPath có vẻ như nó có thể dẫn đến một truy vấn khá đói tài nguyên.
redcalx

1
@thelocster: cái này không khác gì truy cập dữ liệu thông thường. Các kỹ thuật để cải thiện hiệu suất XML đã được ghi lại đầy đủ. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Remus Rusanu

2
hãy nhớ rằng nếu XML của bạn có các không gian tên xmlns được xác định, bạn sẽ cần xác định các không gian đó trong biểu thức XQuery (XPath) ở trên. Xem stackoverflow.com/a/1302150/656010 để làm ví dụ.
Tom Wayson

Hơi khác với những gì tôi đang cần, nhưng đây là một giải pháp hoàn hảo cho vấn đề tôi đang gặp phải là nhiều hàng với một cột XML - tôi muốn lặp qua các hàng và kéo ra các trường dữ liệu từ bên trong cột XML và đưa chúng vào một câu lệnh chèn. Vì vậy, 5 hàng, mỗi hàng cho 3 cột dữ liệu trong trường XML = 15 lần chèn, hoàn hảo.
dan richardson

17

Bài đăng này rất hữu ích để giải quyết vấn đề của tôi có định dạng XML hơi khác ... XML của tôi chứa danh sách các khóa như ví dụ sau và tôi lưu trữ XML trong cột SourceKeys trong một bảng có tên DeleteBatch:

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

Tạo bảng và điền vào bảng một số dữ liệu:

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

Đây là SQL của tôi để chọn các khóa từ XML:

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

Đây là kết quả truy vấn ...

Chìa khóa ExecutionKey
1 1
1 2
1 3
2 100
2 101

9

Điều này có thể trả lời câu hỏi của bạn:

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. Đây là một chủ đề thực sự hữu ích để khám phá.

Tôi vẫn thấy một số đề xuất này khó hiểu. Bất cứ khi nào tôi sử dụng valuevới [1]trong chuỗi, nó sẽ chỉ lấy giá trị đầu tiên. Và một số gợi ý được đề xuất sử dụngcross apply (trong các thử nghiệm của tôi) chỉ mang lại quá nhiều dữ liệu.

Vì vậy, đây là ví dụ đơn giản của tôi về cách bạn tạo một xmlđối tượng, sau đó đọc các giá trị của nó vào một bảng.

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)

Và đây là đầu ra:

nhập mô tả hình ảnh ở đây

Cú pháp kỳ lạ, nhưng với một ví dụ phù hợp, bạn có thể dễ dàng thêm vào các hàm SQL Server của riêng mình.

Nói về điều này, đây là câu trả lời chính xác cho câu hỏi này.

Giả sử bạn có dữ liệu xml của bạn trong một @xmlbiến loại xml(như được minh họa trong ví dụ của tôi ở trên), đây là cách bạn sẽ trả về ba hàng dữ liệu từ xml được trích dẫn trong câu hỏi:

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)

nhập mô tả hình ảnh ở đây


Tôi không hiểu làm thế nào đây là câu trả lời chính xác. OP đang yêu cầu truy vấn một cột từ bảng có kiểu XML và trong trường hợp đó bạn phải sử dụng [1], thứ tự chỉ mục để buộc nó trả về 1 hàng hoặc bạn phải áp dụng cột với nodes()để nhận được cấu trúc có thể có xpath chạy chống lại nó. Mã của bạn không chuyển sang kịch bản đó mà không có nhiều sửa đổi. Bạn đang sử dụng một biến, không phải một cột bảng. Bạn cũng đang lạm dụng query()hàm trả về xml. ví dụ như bạn có thể có chỉx.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Davos

3

Nếu bạn có thể bọc XML của mình trong một phần tử gốc - thì sau đây là giải pháp của bạn:

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)

nhập mô tả hình ảnh ở đây


3

MSSQL sử dụng các quy tắc XPath thông thường như sau:

  • nodename Chọn tất cả các nút có tên "nodename"
  • / Lựa chọn từ nút gốc
  • // Chọn các nút trong tài liệu từ nút hiện tại phù hợp với lựa chọn bất kể chúng ở đâu
  • . Chọn nút hiện tại
  • .. Chọn nút cha của nút hiện tại
  • @ Chọn thuộc tính

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

/ * Ví dụ này sử dụng một biến XML với một lược đồ * /

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('/*')
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.