nodes() 方法 (XML 資料類型)
當您想將 xml 資料類型執行個體切割成關聯式資料時,nodes() 方法會很有用。它可以讓您識別會對應至新資料列的節點。
每個 xml 資料類型執行個體都有隱含提供的內容節點。針對儲存在資料行或變數中的 XML 執行個體,這是指文件節點。文件節點是位在每個 xml 資料類型執行個體最上方的隱含節點。
nodes() 方法的結果,會是一個包含原始 XML 執行個體之邏輯副本的資料列集。在這些邏輯副本中,每個資料列執行個體的內容節點,都會設成可用查詢運算式來識別的節點之一,讓後續的查詢能夠比對這些內容節點來進行導覽。
您可以從資料列集中擷取多個值。例如,您可以將 value() 方法套用至 nodes() 所傳回的資料列集,並從原始的 XML 執行個體中擷取多個值。請注意,將 value() 方法套用至 XML 執行個體時,只會傳回一個值。
以下是常見的語法:
nodes (XQuery) as Table(Column)
- XQuery
是字串常值,一個 XQuery 運算式。如果查詢運算式建構了節點,所建構的這些節點會公開在結果資料列集中。如果查詢運算式的結果是空的序列,資料列集也會是空的。如果查詢運算式以靜態方式產生了序列 (其中包含不可部份完成的值) 而不是產生節點,則會引發靜態錯誤。
- Table(Column)
是結果資料列集的資料表名稱和資料行名稱。
例如,假設您有下列資料表:
T (ProductModelID int, Instructions xml)
資料表中儲存了下列製造指示文件。這裡只顯示部份片段。請注意,文件中有三個製造位置。
<root>
<Location LocationID="10"...>
<step>...</step>
<step>...</step>
...
</Location>
<Location LocationID="20" ...>
...
</Location>
<Location LocationID="30" ...>
...
</Location>
</root>
含有查詢運算式 /root/Location
的 nodes()
方法引動過程,會傳回含有三個資料列的資料列集,每個資料列都含有原始 XML 文件的邏輯副本,且其內容項目會設成其中一個 <Location>
節點:
Product
ModelID Instructions
----------------------------------
1 <root>
<Location LocationID="20" ... />
<Location LocationID="30" .../></root>
1 <root><Location LocationID="10" ... />
<Location LocationID="30" .../></root>
1 <root><Location LocationID="10" ... />
<Location LocationID="20" ... />
</root>
然後您可以使用 xml 資料類型方法來查詢這個資料列集。下列查詢會針對所產生的每個資料列,擷取其中內容項目的子樹:
SELECT T2.Loc.query('.')
FROM T
CROSS APPLY Instructions.nodes('/root/Location') as T2(Loc)
以下是結果:
ProductModelID Instructions
----------------------------------
1 <Location LocationID="10" ... />
1 <Location LocationID="20" ... />
1 <Location LocationID="30" .../>
備註
請注意,所傳回的資料列集保留了類型資訊。您可以將 xml 資料類型方法 (例如 query()、value()、exist() 與 nodes()) 套用至 nodes() 方法的結果。然而,您不能套用 modify() 方法來修改 XML 執行個體。
此外,也不能將資料列集中的內容節點具體化。意即,您不能將它用在 SELECT 陳述式中。但是您可以將它用在 IS NULL 及 COUNT(*) 中。
使用 nodes() 方法的狀況,與使用 OPENXML 相同。它會提供 XML 的資料列集檢視。但是,當您在含有數個 XML 文件資料列的資料表上使用 nodes() 方法時,不必使用資料指標。
請注意,nodes() 方法所傳回的資料列集,是未命名的資料列集。因此,您必須用別名來明確地加以命名。
nodes() 函數不能直接套用至使用者自訂函數的結果。若要在純量使用者自訂函數的結果使用 nodes() 函數,可以將使用者自訂函數的結果指派給變數,或者使用衍生的資料表將資料行別名指派給使用者自訂函數傳回值,然後使用 CROSS APPLY 從別名選取。
下列範例顯示使用 CROSS APPLY
從使用者自訂函數的結果中選取的一個方式。
USE AdventureWorks;
GO
CREATE FUNCTION XTest()
RETURNS xml
AS
BEGIN
RETURN '<document/>';
END;
GO
SELECT A2.B.query('.')
FROM
(SELECT dbo.XTest()) AS A1(X)
CROSS APPLY X.nodes('.') A2(B);
GO
DROP FUNCTION XTest;
GO
範例
A. 針對 xml 類型的變數來使用 nodes() 方法
在下列範例中,有一個 XML 文件,其中含有一個 <Root
> 最上層元素及三個 <row
> 子元素。此查詢使用 nodes()
方法設定個別的內容節點,每個 <row
> 元素各設定一個內容節點。nodes()
方法會傳回含有三個資料列的資料列集。每個資料列都有一個原始 XML 的邏輯副本,其中每個內容節點都在原始文件中識別不同的 <row
> 元素。
接著,查詢會從每個資料列傳回內容節點:
DECLARE @x xml
SET @x='<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>'
SELECT T.c.query('.') AS result
FROM @x.nodes('/Root/row') T(c)
GO
以下為結果。在此範例中,查詢方法會傳回內容項目及其內容:
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3"/>
將父系存取子套用在內容節點上,會為所有的三個資料列傳回 <Root
> 元素:
SELECT T.c.query('..') AS result
FROM @x.nodes('/Root/row') T(c)
go
以下是結果:
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>
下列查詢指定了絕對路徑。在內容節點上使用絕對路徑運算式的查詢,將會在內容節點的根節點上啟動。因此,針對 nodes()
所傳回的每個內容節點,您都會收到三個資料列。
SELECT T.c.query('/Root/row') AS result
FROM @x.nodes('/Root/row') T(c)
GO
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
請注意,您不能直接使用 xml 資料類型之 nodes()
方法所傳回的資料行。例如,下列查詢會傳回錯誤:
...
SELECT T.c
FROM @x.nodes('/Root/row') T(c)
在下列查詢中,xml 資料類型的 value()
方法及 query()
方法,會套用至 nodes()
方法所傳回的資料列集。value()
方法會傳回內容項目 (<row
>) 的 id
屬性,而 query()
方法會傳回內容項目的 <name
> 元素子樹。
DECLARE @x xml
SET @x='
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>Joe</name></row>
<row id="3" />
</Root>
'
SELECT T.c.value('@id','int') as id,
T.c.query('name') as NAME
FROM @x.nodes('/Root/row') T(c)
GO
以下是結果:
id NAME
-----------------------
1 <name>Larry</name>
2 <name>Joe</name>
3
請注意,此結果包含資料列識別碼 3
,而且 <row
> 元素沒有 <name
> 子系。如果您要篩選結果,以傳回或不傳回不含 <name
> 子系的資料列,您可以用下列其中一種方法來篩選:
- 在
nodes()
路徑運算式中使用述詞,例如/Root/row[name]
。 - 在資料列集上使用 exist() 方法。
- 使用 CROSS APPLY。
- 使用 OUTER APPLY。
下列查詢會針對nodes()
所傳回的資料列集指定 exist()
方法。如果內容節點 (<row
>) 具有 <name
> 子系,則 exist()
方法會傳回 True。
DECLARE @x xml
SET @x='<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>Joe</name></row>
<row id="3" />
</Root>'
SELECT T1.rows.value('@id','int') as id
FROM @x.nodes('/Root/row') T1(rows)
WHERE T1.rows.exist('name') = 1;
GO
這會傳回二個資料列:資料列識別碼 1 和 2。
下列查詢使用 OUTER APPLY
。OUTER APPLY
會將 nodes()
套用至 T1(rows)
中的每個資料列,並傳回會產生結果集或產生 NULL 的資料列。因此,再使用 WHERE 子句來篩選資料列,只擷取 T2.names
資料行不是 NULL 的資料列。
DECLARE @x xml
SET @x='
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>Joe</name></row>
<row id="3" />
</Root>'
SELECT T1.rows.value('@id','int') as id
FROM @x.nodes('/Root/row') T1(rows)
OUTER APPLY T1.rows.nodes('./name') as T2(names)
WHERE T2.names IS NOT NULL
GO
下列查詢使用 CROSS APPLY
。CROSS APPLY
會將 nodes()
套用至外部資料表 T1(rows)
中的每個資料列,並且只傳回將 nodes()
套用至 T1.rows
時,會產生結果集的資料列。在此情況下,您不必使用 WHERE 子句來測試 IS NOT NULL。
DECLARE @x xml
SET @x='<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>Joe</name></row>
<row id="3" />
</Root>'
SELECT T1.rows.value('@id','int') as id
FROM @x.nodes('/Root/row') T1(rows)
CROSS APPLY T1.rows.nodes('./name') as T2(names)
GO
如需 CROSS APPLY 及 OUTER APPLY 的詳細資訊,請參閱<使用 APPLY>。
B. 針對 xml 類型的資料行來指定 nodes() 方法
此範例使用自行車製造指示,並將其儲存在 ProductModel 資料表的 Instructions xml 類型資料行中。如需詳細資訊,請參閱<在 AdventureWorks 資料庫中的 xml 資料類型表示法>。
在下列範例中,會針對 ProductModel
資料表中 xml 類型的 Instructions
資料行來指定 nodes()
方法。
nodes()
方法藉由指定 /MI:root/MI:Location
路徑,將 <Location
> 元素設為內容節點。結果資料列集包含原始文件的邏輯副本 (文件中的每個 <Location
> 節點各有一個副本),且其內容節點設為 <Location
> 元素。因此,nodes()
函數會提供一組 <Location
> 內容節點。
用於此資料列集的 query()
方法會要求 self::node
,所以會傳回每個資料列的 <Location>
元素。
在此範例中,查詢在特定產品型號的製造指示文件中,將每個 <Location
> 元素都設成內容節點。您可以使用這些內容節點來擷取值,如下所示:
- 尋找每個 <
Location
> 中的 Location ID。 - 擷取每個 <
Location
> 中的製造步驟 (<step
> 子元素)
此查詢會傳回內容項目,其中在 query()
方法中指定了 self::node()
的 '.'
縮寫語法。
請注意下列事項:
nodes()
方法會套用至 Instructions 資料行,並傳回資料列集T (C)
。此資料列集包含原始製造指示文件的邏輯副本,並以/root/Location
做為內容項目。CROSS APPLY 會將
nodes()
套用在Instructions
資料表中的每個資料列,並且只傳回會產生結果集的資料列。SELECT C.query('.') as result FROM Production.ProductModel CROSS APPLY Instructions.nodes(' declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions"; /MI:root/MI:Location') as T(C) WHERE ProductModelID=7
以下是部份結果:
<MI:Location LocationID="10" ...> <MI:step ... /> ... </MI:Location> <MI:Location LocationID="20" ... > <MI:step ... /> ... </MI:Location> ...
下列查詢類似上一個查詢,但它是藉由資料列集中的內容節點,使用 value()
及 query()
來擷取一組值。SELECT
子句會針對每個位置,擷取其 Location ID 及該位置所使用的工具。
SELECT C.value('@LocationID','int') as LId,
C.query('declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";
MI:step/MI:tool') as result
FROM Production.ProductModel
CROSS APPLY Instructions.nodes('
declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";
/MI:root/MI:Location') as T(C)
WHERE ProductModelID=7
以下為結果。為方便讀取,沒有顯示命名空間。
LId result
10 <MI:tool xmlns:MI="...">T-85A framing tool</MI:tool>
<MI:tool xmlns:MI="...">Trim Jig TJ-26</MI:tool>
<MI:tool xmlns:MI="...">router with a carbide tip 15</MI:tool>
<MI:tool xmlns:MI="...">Forming Tool FT-15</MI:tool>
20
30 <MI:tool xmlns:MI="...">standard debur tool</MI:tool>
45 <MI:tool xmlns:MI="...">paint harness</MI:tool>
50
60
C. 將 nodes() 套用到由另一個 nodes() 方法傳回的資料列集
下列程式碼會在 XML 文件中查詢 ProductModel
資料表之 Instructions
資料行中的製造指示。該查詢會傳回一個資料列集,其中包含產品型號識別碼、製造位置,以及製造步驟。
請注意下列事項:
nodes()
方法會套用至Instructions
資料行,並傳回T1 (Locations)
資料列集。此資料列集包含原始製造指示文件的邏輯副本,並以/root/Location
元素做為項目內容。nodes()
會套用至T1 (Locations)
資料列集,並傳回T2 (steps)
資料列集。此資料列集包含原始製造指示文件的邏輯副本,並以/root/Location/step
元素做為項目內容。
SELECT ProductModelID, Locations.value('./@LocationID','int') as LocID,
steps.query('.') as Step
FROM Production.ProductModel
CROSS APPLY Instructions.nodes('
declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";
/MI:root/MI:Location') as T1(Locations)
CROSS APPLY T1.Locations.nodes('
declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";
./MI:step ') as T2(steps)
WHERE ProductModelID=7
GO
以下是結果:
ProductModelID LocID Step
----------------------------
7 10 <step ... />
7 10 <step ... />
...
7 20 <step ... />
7 20 <step ... />
7 20 <step ... />
...
此查詢會宣告二次 MI
前置詞。您也可以改用 WITH XMLNAMESPACES
來宣告一次前置詞,並且在查詢中使用該前置詞:
WITH XMLNAMESPACES (
'https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions' AS MI)
SELECT ProductModelID, Locations.value('./@LocationID','int') as LocID,
steps.query('.') as Step
FROM Production.ProductModel
CROSS APPLY Instructions.nodes('
/MI:root/MI:Location') as T1(Locations)
CROSS APPLY T1.Locations.nodes('
./MI:step ') as T2(steps)
WHERE ProductModelID=7
GO
下列查詢與上一個查詢類似,但它是將 exist()
方法套用至 T2(steps)
資料列集中的 XML,只擷取至少使用一項製造工具的製造步驟。意即,<step
> 元素至少有一個 <tool
> 子系。
WITH XMLNAMESPACES (
'https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions' AS MI)
SELECT ProductModelID,
Locations.value('./@LocationID','int') as LocID,
steps.query('.') as Steps
FROM Production.ProductModel
CROSS APPLY Instructions.nodes('/MI:root/MI:Location') as T1(Locations)
CROSS APPLY T1.Locations.nodes('./MI:step') as T2(steps)
WHERE ProductModelID=7
AND steps.exist('./MI:tool') = 1
GO
請參閱
概念
使用 WITH XMLNAMESPACES 來加入命名空間
XML 資料類型
產生 XML 執行個體
XML 應用程式範例