XQuery 概述
DB2 9 提供了對 XQuery 的支持。XQuery 是一種專門為操作 XML 數據而設計的新的查詢語言,它是 W3C 行業標准的一部分。XQuery 使用戶能夠在 XML 文檔固有的層次結構中導航。因此,可以使用 XQuery 檢索 XML 文檔或文檔片段。還可以編寫包含基於 XML 的謂詞的 XQuery,從而將不需要的數據從 DB2 將返回的結果中 “過濾出去”。XQuery 提供了許多其他功能,比如對 XML 輸出進行轉換以及將條件邏輯合並到查詢中。
在學習如何使用 XQuery 之前,需要了解這種語言的一些基本概念。
XQuery 基礎
XQuery 總是將 XQuery Data Model 的一個值轉換為 XQuery Data Model 的另一個值。XQuery Data Model 中的一個值是由零個或更多條目組成的序列。條目可以是:
任何原子值
XML 節點(有時候稱為 XML 文檔片段),比如元素、屬性或文本節點
完整的 XML 文檔
XQuery 的輸入常常是一個 XML 文檔集合。
清單 1 顯示一個 XML 文檔,其中包含 8 個元素節點、1 個屬性節點和 6 個文本節點。元素節點由元素標記表示。在這個文檔中,Client、Address、street、city、state、zip 和兩個 email 元素都是元素節點。如果仔細看看 ClIEnt 元素,就會發現它包含一個屬性節點,客戶的 id。文檔的一些元素節點有相關聯的文本節點。例如,city 元素的文本節點是 San Jose。
清單 1. 示例 XML 文檔
<ClIEnt id="123">
<Address>
<street>9999 Elm St.</street>
<city>San Jose</city>
<state>CA</state>
<zip>95141</zip>
</Address>
<email>[email protected]</email>
<email>[email protected]</email>
</ClIEnt>
圖 1 顯示這個示例文檔中的節點。
圖 1. 示例 XML 文檔中的元素、屬性和文本節點XQuery 語言來源於 XPath(定義用戶如何在 XML 文檔中導航)和 XML Schema(讓用戶能夠為文檔指定有效的結構和數據類型)等其他 XML 標准。在本教程中,學習如何將 XPath 表達式結合到 XQuery 中。
XQuery 提供了幾種不同的表達式,可以按照自己喜歡的任何方式組合使用它們。每個表達式都返回一系列值,這些值可以用作其他表達式的輸入。最外層表達式的結果就是查詢的結果。
本教程討論兩種重要的 XQuery 表達式:
路徑表達式 允許用戶在 XML 文檔的層次結構中導航(或者說 “漫游”)並返回在路徑末端找到的節點。 FLWOR 表達式 它很像 SQL 中的 SELECT-FROM-WHERE 表達式。它用來遍歷一系列條目並可選地返回每個條目的某些計算結果。XQuery 與 SQL 的差異
許多 SQL 用戶誤認為 XQuery 與 SQL 非常相似。但是,XQuery 在許多方面與 SQL 差異很大,因為設計這種語言的目的是操縱具有不同性質的不同數據模型。XML 文檔包含層次結構並有固有的次序。與之相反,關系 DBMS(或者更准確地說,基於 SQL 的 DBMS)支持的表是平面的和基於集合的,所以行是無序的。
數據模型中的這些差異使得用來支持它們的查詢語言有很大差異。例如,XQuery 使程序員能夠在 XML 的層次結構中導航。一般的 SQL(不帶 XML 擴展)沒有(也不需要)在表數據結構中 “導航” 的表達式。XQuery 支持有類型數據和無類型數據,而 SQL 數據總是要用特定的類型進行定義。
XQuery沒有空值(null),因為 XML 文檔忽略缺失的或未知的數據。SQL 使用空值表示缺失的或未知的數據。XQuery 返回 XML 數據的序列;SQL 返回各種 SQL 數據類型的結果集。最後,XQuery 只操作 XML 數據。SQL 操作按照傳統 SQL 類型定義的列,SQL/XML(帶 XML 擴展的 SQL)操作 XML 數據和傳統類型的 SQL 數據。
XQuery 中的路徑表達式
XQuery 支持 XPath 表達式,使用戶能夠在 XML 文檔層次結構中導航,找到他們所需要的部分。詳細討論 XPath 超出了本教程的范圍,但是我們在這裡要看幾個簡單的示例。
XPath 表達式看起來非常像在操作傳統計算機文件系統時使用的表達式。考慮一下在 Unix 或 Windows 目錄中是如何導航的,就能夠理解使用 XPath 在 XML 文檔中進行導航的方式。
XQuery 中的路徑表達式由一系列 “步” 組成,步之間由斜線字符分隔。在最簡單的形式中,每一步在 XML 層次結構中下降一層,尋找前一步返回的元素的子元素。路徑表達式中的每一步還可以包含一個謂詞,它對這一步返回的元素進行過濾,只保留滿足條件的元素。稍後將看到這樣的一個示例。
一種常見的任務是從 XML 文檔根(XML 層次結構的最頂層)開始導航,尋找感興趣的某個節點。例如,要想獲得清單 1 的文檔中的 email 元素,可以編寫下面的表達式:
清單 2. 導航到 email 元素
/ClIEnt/email
如果文檔包含多個 email 元素,而您只想獲得第一個,那麼可以編寫:
清單 3. 導航到第一個 email 元素
/ClIEnt/email[1]
除了在路徑表達式中指定元素節點之外,還可以使用 @ 符號指定屬性節點,從而在元素中識別出屬性。下面這個路徑表達式導航到 id 屬性等於 123 的 ClIEnt 元素中的第一個 email 元素:
清單 4. 指定屬性節點和值
/ClIEnt[@id='123']/email[1]
前面這個示例使用了一個基於屬性值的過濾謂詞。還可以根據其他節點值進行過濾。XPath 用戶常常根據元素值進行過濾,比如下面的表達式返回住在加利福尼亞的客戶的 zip 元素:
清單 5. 根據元素值進行過濾
/ClIEnt/Address[state="CA"]/zip
可以使用通配符(“*”)匹配路徑表達式中各個步上的任何節點。下面的示例獲取在 ClIEnt 元素的任何直接子元素下找到的任何 city 元素。
清單 6. 使用通配符
/ClIEnt/*/city
對於我們的示例文檔,這將返回值為 San Jose 的 city 元素。導航到這個 city 元素的更精確的方法是:
清單 7. 導航到 city 元素的更精確的方法
/ClIEnt/Address/city
清單 8 給出幾個其他類型的路徑表達式示例。
清單 8. 更多路徑表達式及其含義
//* (獲取文檔中的所有節點)
//email (尋找文檔中任何地方的 email 元素)
/Client/email[1]/text() (獲得 ClIEnt 元素下第一個 email 元素的文本節點)
/Client/Address/* (選擇根 ClIEnt 元素的 Address 子元素的所有子節點)
/Client/data(@id) (返回 ClIEnt 元素的 id 屬性的值)
/ClIEnt/Address[state="CA"]/../email (尋找地址在加利福尼亞的客戶的 email 元素。
“..” 這一步導航回 Address 節點的父元素。)
注意,XPath 是大小寫敏感的。在編寫 XQuery 時記住一點很重要,因為這是 XQuery 與 SQL 不同的一個方面。例如,如果將路徑表達式 “/clIEnt/address” 放進 XQuery,對於 清單 1 中的示例文檔,不會返回任何結果。
XQuery 中的 FLWOR 表達式
人們常常提到 XQuery 中的 FLWOR 表達式。與 SQL 中的 SELECT-FROM-WHERE 塊一樣,XQuery FLWOR 表達式可以包含多個由關鍵字指示的子句。FLWOR 表達式的子句以下面的關鍵字開頭:
for:遍歷一個輸入序列,依次將一個變量綁定到每個輸入條目
let:聲明一個變量並給它賦值,值可以是包含多個條目的列表
where:指定對查詢結果進行過濾的標准
order by:指定結果的排序次序
return:定義返回的結果
我們來簡要介紹一下每個關鍵字。我們將在一節中討論 for 和 return,所以可以看到一個完整的示例。(如果沒有 return 子句,表達式就不完整。)
for 和 return for 和 return 關鍵字用來遍歷一系列值並為每個值返回某些結果。下面是一個非常簡單的示例:for $i in (1, 2, 3)
return $i
在 XQuery 中,變量名前面有一個美元符號(“$”)。所以,前面的示例將數字 1、2 和 3 綁定到變量 $i(每次綁定一個數字),並對於每次綁定返回 $i 的值。前面表達式的輸出是 3 個值的序列:
1
2
3
看一個示例應該有助於澄清這一差異。請考慮下面這個使用 for 關鍵字的表達式,並注意返回的輸出:
for $i in (1, 2, 3)
return <output> {$i} </output>
<output>1</output>
<output>2</output>
<output>3</output>
表達式的最後一行要求為每次迭代返回一個名為 output 的新元素。這個元素的值是 $i 的值。因為 $i 依次設置為數字值 1、2 和 3,所以這個 XQuery 表達式返回 3 個 output 元素,它們具有不同的值。
現在考慮使用 let 關鍵字的類似表達式:
let $i := (1, 2, 3)
return <output>{$i}</output>
<output>1 2 3</output>
輸出很不一樣。輸出只有一個 output 元素,它的值是 “1 2 3”。
這兩個示例說明了一個重點:for 關鍵字遍歷輸入序列中的條目(每次一個),並將每個條目依次綁定到一個指定的變量。與之相反,let 關鍵字將輸入序列中的所有條目同時綁定到一個指定的變量。
where 在 XQuery 中,where 的功能很像 SQL 中的 WHERE 子句:它使用戶能夠將過濾標准應用於查詢。考慮以下示例:for $i in (1, 2, 3)
where $i < 3
return <output>{$i}</output>
<output>1</output>
<output>2</output>
order by 使用戶能夠讓返回的結果按照指定的次序排序。考慮以下 XQuery 表達式和它的輸出(輸出沒有按照任何用戶指定的次序進行排序):
for $i in (5, 1, 2, 3)
return $i
5
1
2
3
可以使用 order by 關鍵字對結果進行排序。下面的示例使返回的結果按照降序排序:
for $i in (5, 1, 2, 3)
order by $i descending
return $i
5
3
2
1
DB2 對 XQuery 的支持
DB2 把 XQuery 當作一類語言,這允許用戶直接編寫 XQuery 表達式,而不需要將 XQuery 嵌入或包裝到 SQL 語句中。DB2 的查詢引擎將原生地處理 XQuery,這意味著它直接分析、評估和優化 XQuery,而不需要在幕後將它們轉換為 SQL。如果願意,可以編寫同時包含 XQuery 和 SQL 的 “多語種” 查詢。DB2 也會處理和優化這些查詢。
要在 DB2 中直接執行 XQuery,必須在查詢前面加上關鍵字 xquery。這指示 DB2 調用它的 XQuery 分析器來處理請求。如果將 XQuery 作為最外層的(頂級)語言使用,那麼只需這麼做就夠了。如果將 XQuery 表達式嵌入 SQL 中,那麼不需要在前面加上 xquery 關鍵字。
在本教程中,將以 XQuery 作為主要語言,所以這裡給出的所有查詢都在開頭處加上 xquery 關鍵字。
示例數據庫環境
為了幫助您學習 XQuery,本教程建立一個包含 XML 文檔的 “clIEnts” 示例表。下面幾節詳細地解釋這個表及其內容,並描述 DB2 提供的可以用來執行 XQuery 的功能。
如果希望在自己的 DB2 系統中設置這個示例表和內容,那麼可以使用腳本 tutorial.sql。它包含本節所示的所有代碼。
示例表
示例中的 clIEnts 表包含幾個傳統 SQL 數據類型(比如整數和可變長度的字符串)的列,還有一個新的 SQL “XML” 數據類型的列。
前三列記錄客戶的 ID、姓名和狀態信息。status 列的典型值包括 Gold、Silver 和 Standard。第四列包含每個客戶的聯系信息,比如家庭的郵政地址、電話號碼、電子郵件地址等等。這些信息存儲在良構的 XML 文檔中。
下面是 clIEnts 表的定義:
清單 9. clIEnts 表的定義
create table clIEnts(
id int primary key not null,
name varchar(50),
status varchar(10),
contactinfo XML
);
示例 XML 文檔
在研究如何編寫針對這個表的 XQuery 之前,需要用一些示例數據填充它。下面的 SQL 語句將 6 行數據插入 clIEnts 表。每行都包含一個 XML 文檔,每個 XML 文檔的結構都有所不同。例如,有的客戶有電子郵件地址,而其他客戶沒有。
清單 10. clIEnts 表的示例數據
insert into clIEnts values (3227, 'Ella Kimpton', 'Gold',
'<ClIEnt>
<Address>
<street>5401 Julio Ave.</street>
<city>San Jose</city>
<state>CA</state>
<zip>95116</zip>
</Address>
<phone>
<work>4084630000</work>
<home>4081111111</home>
<cell>4082222222</cell>
</phone>
<fax>4087776666</fax>
<email>[email protected]</email>
</ClIEnt>'
);
insert into clIEnts values (8877, 'Chris Bontempo', 'Gold',
'<ClIEnt>
<Address>
<street>1204 Meridian Ave.</street>
<apt>4A</apt>
<city>San Jose</city>
<state>CA</state>
<zip>95124</zip>
</Address>
<phone>
<work>4084440000</work>
</phone>
<fax>4085555555</fax>
</ClIEnt>'
);
insert into clIEnts values (9077, 'Lisa Hansen', 'Silver',
'<ClIEnt>
<Address>
<street>9407 Los Gatos Blvd.</street>
<city>Los Gatos</city>
<state>CA</state>
<zip>95032</zip>
</Address>
<phone>
<home>4083332222</home>
</phone>
</ClIEnt>'
);
insert into clIEnts values (9177, 'Rita Gomez', 'Standard',
'<ClIEnt>
<Address>
<street>501 N. First St.</street>
<city>Campbell</city>
<state>CA</state>
<zip>95041</zip>
</Address>
<phone>
<home>4081221331</home>
<cell>4087799881</cell>
</phone>
<email>[email protected]</email>
</ClIEnt>'
);
insert into clIEnts values (5681, 'Paula Lipenski', 'Standard',
'<ClIEnt>
<Address>
<street>1912 Koch Lane</street>
<city>San Jose</city>
<state>CA</state>
<zip>95125</zip>
</Address>
<phone>
<cell>4085430091</cell>
</phone>
<email>[email protected]</email>
<email>[email protected]</email>
</ClIEnt>'
);
insert into clIEnts values (4309, 'Tina Wang', 'Standard',
'<ClIEnt>
<Address>
<street>4209 El Camino Real</street>
<city>Mountain VIEw</city>
<state>CA</state>
<zip>95033</zip>
</Address>
<phone>
<home>6503310091</home>
</phone>
</ClIEnt>'
);
查詢環境
本教程中的所有查詢都設計為交互式地執行。可以通過 DB2 命令行處理程序或 DB2 Control Center 的 DB2 Command Editor 執行這些查詢。本教程中的示例使用 DB2 命令行處理程序。(DB2 還提供了基於 Eclipse 的 Developer Workbench,可以幫助您以圖形化方式構造 XQuery,但是對 Developer Workbench 的討論超出了本教程的范圍。)
可以修改 DB2 命令行處理程序的默認設置,以便更容易處理 XML 數據。例如,下面的命令(從一個 DB2 命令窗口中發出)將以某種方式啟動 DB2 命令行處理程序,這種方式讓 XQuery 結果以容易閱讀的格式顯示:
清單 11. 設置 DB2 命令行處理選項
db2 -i -d
這個命令使 DB2 在顯示的 XQuery 結果中增加額外的空白。DB2 實際上並不將這些空白添加到數據中。應用程序看到的返回條目中也沒有額外的空白 —— 這些空白只出現在 DB2 命令行處理程序窗口中。
簡單的 XML 數據檢索操作
在本節中,將學習如何編寫檢索整個 XML 文檔和 XML 文檔的特定部分(即片段)的 XQuery。為此,將使用 XPath 表達式和 FLWOR 表達式。
檢索 DB2 中存儲的完整 XML 文檔
在作為頂級語言運行時,XQuery 需要一個輸入數據的來源。在 DB2 中,指定輸入數據來源的一種方法是調用函數 db2-fn:xmlcolumn。這個函數有一個輸入參數,這個參數標識用戶感興趣的 DB2 表和 XML 列。db2-fn:xmlcolumn 函數返回給定的列中存儲的 XML 文檔序列。例如,以下查詢返回包含客戶聯系信息的 XML 文檔序列:
清單 12. 返回客戶聯系數據的簡單 XQuery
xquery
db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')
您可能會奇怪,為什麼這個查詢中指定的表和列名是大寫的。如果回憶一下前面用來創建這個表的 SQL 語句,就會知道表和列名是小寫的。除非另外指定,DB2 會在內部編目表中將表和列名轉換為大寫。因為 XQuery 是大小寫敏感的,所以小寫的表和列名與 DB2 編目中的大寫名稱不匹配。
現在,考慮這個 XQuery 的輸出。對於插入 clIEnts 表的 示例數據,清單 12 中查詢的輸出是 6 個 XML 文檔,如下所示。
清單 13. 前一個查詢的輸出
<?XML version="1.0" encoding="Windows-1252" ?>
<ClIEnt>
<Address>
<street>
5401 Julio Ave.
</street>
<city>
San Jose
</city>
<state>
CA
</state>
<zip>
95116
</zip>
</Address>
<phone>
<work>
4084630000
</work>
<home>
4081111111
</home>
<cell>
4082222222
</cell>
</phone>
<fax>
4087776666
</fax>
<email>
[email protected]
</email>
</ClIEnt>
<?XML version="1.0" encoding="Windows-1252" ?>
<ClIEnt>
<Address>
<street>
1204 Meridian Ave.
</street>
<apt>
4A
</apt>
<city>
San Jose
</city>
<state>
CA
</state>
<zip>
95124
</zip>
</Address>
<phone>
<work>
4084440000
</work>
</phone>
<fax>
4085555555
</fax>
</ClIEnt>
<?XML version="1.0" encoding="Windows-1252" ?>
<ClIEnt>
<Address>
<street>
9407 Los Gatos Blvd.
</street>
<city>
Los Gatos
</city>
<state>
CA
</state>
<zip>
95032
</zip>
</Address>
<phone>
<home>
4083332222
</home>
</phone>
</ClIEnt>
<?XML version="1.0" encoding="Windows-1252" ?>
<ClIEnt>
<Address>
<street>
501 N. First St.
</street>
<city>
Campbell
</city>
<state>
CA
</state>
<zip>
95041
</zip>
</Address>
<phone>
<home>
4081221331
</home>
<cell>
4087799881
</cell>
</phone>
<email>
[email protected]
</email>
</ClIEnt>
<?XML version="1.0" encoding="Windows-1252" ?>
<ClIEnt>
<Address>
<street>
1912 Koch Lane
</street>
<city>
San Jose
</city>
<state>
CA
</state>
<zip>
95125
</zip>
</Address>
<phone>
<cell>
4085430091
</cell>
</phone>
<email>
[email protected]
</email>
<email>
[email protected]
</email>
</ClIEnt>
<?XML version="1.0" encoding="Windows-1252" ?>
<ClIEnt>
<Address>
<street>
4209 El Camino Real
</street>
<city>
Mountain VIEw
</city>
<state>
CA
</state>
<zip>
95033
</zip>
</Address>
<phone>
<home>
6503310091
</home>
</phone>
</ClIEnt>
如果您有興趣,還可以使用一般的 SQL 檢索 contactinfo 列中包含的完整 XML 文檔集。簡單的 "select contactinfo from clIEnt" 語句就能夠完成這個任務。
檢索特定的 XML 元素
用戶常常希望檢索 XML 文檔中的特定元素。用 XQuery 完成這個任務很容易。假設希望檢索所有提供了傳真號的客戶的傳真號。下面是編寫這種查詢的一種方法:
清單 14. 檢索客戶傳真號的 FLWOR 表達式
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/fax
return $y
第一行指示 DB2 調用它的 XQuery 分析器。下一行指示 DB2 遍歷 CLIENTS.CONTACTINFO 列中包含的 ClIEnt 元素的 fax 子元素。每個 fax 元素依次綁定到變量 $y。第三行指示對於每次迭代返回 $y 的值。結果是一系列 XML 元素,如下所示。
清單 15. 前一個查詢的示例輸出
<fax>4087776666</fax>
<fax>4085555555</fax>
(這裡顯示的輸出經過了簡化。XML 版本信息已經去掉了,因為它對於本教程並不重要。但是,在 DB2 中運行的 XQuery 都會返回這些信息。示例見 清單 13。)
清單 14 所示的查詢也可以表示為一個三步的路徑表達式:
清單 16. 檢索客戶傳真號的路徑表達式
xquery
db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/fax
在 XQuery 基礎 一節中,了解了文本節點。我們在這裡應用這一知識。假設不希望從查詢獲得 XML 片段,而是獲得 XML 元素值的文本表示。為此,可以在 return 子句中調用 text() 函數:
清單 17. 檢索客戶傳真號的文本表示的兩個查詢
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/fax
return $y/text()
(或)
xquery
db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/fax/text()
這些查詢的輸出是:
清單 18. 前兩個查詢的示例輸出
4087776666
4085555555
本節中前面的查詢的結果相當簡單,因為它們都涉及 fax 元素,這個元素基於一種原始數據類型。當然,元素可能基於復雜的類型,可能包含子元素(或嵌套的層次結構)。客戶聯系信息的 Address 元素就是這種元素的例子。考慮以下 XQuery 會返回什麼:
清單 19. 檢索復雜 XML 類型的 XQuery
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/Address
return $y
(或)
xquery
db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/Address
如果您猜測會返回一系列 XML 片段,其中包含 Address 元素及其所有子元素,那麼您猜對了。下面是輸出的摘錄:
清單 20. 前面查詢的部分輸出
<Address>
<street>5401 Julio Ave.</street>
<city>San Jose</city>
<state>CA</state>
<zip>95116</zip>
</Address>
<Address>
<street>1204 Meridian Ave.</street>
<apt>4A</apt>
<city>San Jose</city>
<state>CA</state>
<zip>95124</zip>
</Address>
<Address>
<street>9407 Los Gatos Blvd.</street>
<city>Los Gatos</city>
<state>CA</state>
<zip>95032</zip>
</Address>
. . .
根據 XML 元素值進行過濾的查詢
用戶常常希望在 XQuery 中指定基於 XML 的過濾條件。這也很容易。在本節中,您將看到如何讓前面的 XQuery 示例更有選擇性。
指定單一過濾謂詞
我們先研究一下如何返回郵政編碼為 95116 的所有客戶的郵政地址。可以將 where 子句結合進 XQuery,從而根據 DB2 中存儲的示例 XML 文檔中的 zip 元素值對結果進行過濾。將一個 where 子句添加到 清單 19 中的 FLWOR 表達式中,從而只獲得感興趣的地址,如下所示:
清單 21. 帶 “where” 子句的 FLWOR 表達式
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/Address
where $y/zip="95116"
return $y
添加的 where 子句相當簡單。for 子句依次將變量 $y 綁定到每個地址。這個 where 子句包含一個簡短的路徑表達式,它從每個地址導航到其中嵌套的 zip 元素。僅當這個 zip 元素的值等於 95116 時,這個 where 子句才為真(因此獲得這個地址)。
因為只有一個客戶的郵政編碼為 95116,返回的結果是:
清單 22. 前一個查詢的輸出
<Address>
<street>5401 Julio Ave.</street>
<city>San Jose</city>
<state>CA</state>
<zip>95116</zip>
</Address>
可以通過在路徑表達式中添加謂詞來獲得同樣的結果,如下所示:
清單 23. 帶過濾謂詞的路徑表達式
xquery
db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/Address[zip="95116"]
指定多個過濾謂詞
當然,通過根據郵政編碼值進行過濾,也可以返回與街道地址無關的元素。還可以在一個查詢中根據多個 XML 元素值進行過濾。下面的查詢返回那些住在 San Jose 市或者郵政編碼為 95032(這是加利福尼亞 Los Gatos 的郵政編碼)的客戶的電子郵件信息。
清單 24. 用 FLWOR 表達式根據多個 XML 元素值進行過濾
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt
where $y/Address/zip="95032" or $y/Address/city="San Jose"
return $y/email
這個示例修改了 for 子句,從而將變量 $y 綁定到 Client 元素而不是 Address 元素。這樣就可以根據子樹的一部分(Address)對 ClIEnt 元素進行過濾,但是返回子樹的另一部分(email)。where 子句和 return 子句中的路徑表達式必須相對於綁定到變量(在這個示例中是 $y)的元素。
同樣的查詢可以更精確地表示為路徑表達式:
清單 25. 用路徑表達式根據多個 XML 元素值進行過濾
xquery
db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt[Address/zip="95032"
or Address/city="San Jose"]/email;
對於 clIEnts 表中的示例數據,前面兩個查詢的輸出是:
清單 26. 查詢輸出
<email>
[email protected]
</email>
<email>
[email protected]
</email>
<email>
[email protected]
</email>
XQuery 和 SQL 之間差異的實際示例
如果觀察 清單 26 中的查詢輸出,您可能會發現返回的結果在兩個方面與 SQL 程序員的預期有顯著差異:
對於沒有提供電子郵件地址的客戶,沒有返回 XML 數據。
在我們的 示例數據 中,有四個客戶滿足查詢的選擇條件(住在 San Jose 或者郵政編碼為 95032),但是這一事實沒有反映在查詢結果中。為什麼呢?因為其中兩個客戶的記錄中沒有 email 元素。因為 XQuery 不使用空值,這些 “缺失的” 信息不會反映在結果中。
輸出沒有表明哪些電子郵件地址來自同一個 XML 文檔。
仔細看 示例數據,就會發現 清單 26 所示的最後兩個電子郵件地址包含在同一個 XML 文檔中(也就是說,它們屬於同一個客戶)。這一點在輸出中看不出來。
在某些情況下,這兩種表現可能正是我們需要的,但在其他情況下可能不理想。例如,如果希望向記錄的每個帳號發送一封電子郵件,那麼會遍歷 XML 格式的客戶電子郵件地址列表,這在應用程序中很容易辦到。但是,如果希望向每個客戶只發送一次通知,包括那些只提供了街道地址的客戶,那麼前面的 XQuery 就不夠了。
可以以多種方式改寫前面的查詢,讓返回的結果以某種方式表達出缺失的信息,並表明多個電子郵件地址來自同一個客戶記錄(即,同一個 XML 文檔)。在本教程後面,您將學到如何編寫這樣的查詢。但是,如果只是希望返回的列表中對於每個客戶只包含一個電子郵件地址,那麼只需稍稍修改前面的查詢:
清單 27. 返回客戶的第一個 email 元素
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt
where $y/Address/zip="95032" or $y/Address/city="San Jose"
return $y/email[1]
(或)
xquery
db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt[Address/zip="95032"
or Address/city="San Jose"]/email[1];
這兩個查詢都指示 DB2 返回在符合條件的 XML 文檔(客戶聯系記錄)中找到的第一個 email 元素。如果對於某個符合條件的客戶沒有找到電子郵件地址,那麼它不返回這個客戶的任何信息。因此,這兩個查詢產生下面的輸出:
清單 28. 查詢輸出
<email>
[email protected]
</email>
<email>
[email protected]
</email>
XML 數據轉換
XQuery 的一個強大功能是能夠將 XML 數據從一種形式的 XML 轉換為另一種。在本節中,您將了解進行 XML 數據轉換是多麼容易。
將 XML 轉換為 Html
在基於 Web 的應用程序中,常常需要將全部或部分 XML 文檔轉換為 HTML 以便進行顯示。利用 XQuery 很容易完成這個過程。請考慮以下查詢,它檢索客戶的地址、按郵政編碼對結果進行排序並將輸出轉換為 XML 元素,這些元素是一個無序 Html 列表的組成部分:
清單 29. 查詢 DB2 XML 數據並返回 Html 形式的結果
xquery
<ul> {
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt/Address
order by $y/zip
return <li>{$y}</li>
} </ul>
這個查詢以 xquery 關鍵字開頭,從而向 DB2 分析器表示 XQuery 用作主要語言。第二行使無序列表的 Html 標記(<ul>)出現在結果中。它還引入一個花括號,這是這個查詢中使用的兩對花括號中的第一對。花括號讓 DB2 計算並處理其中包含的表達式,而不是將它作為字符串對待。
第三行遍歷客戶的地址,依次將變量 $y 綁定到每個 Address 元素。第四行包含一個 order by 子句,它指定結果必須根據客戶的郵政編碼(即綁定到 $y 的每個 Address 的 zip 子元素)進行升序排序(這是默認次序)。return 子句表示將 Address 元素包圍在 HTML 列表項標記中,然後再返回。最後一行結束查詢並完成 Html 無序列表標記。
這個查詢的部分輸出如下:
清單 30. 前一個查詢的 Html 輸出
<ul>
<li>
<Address>
<street>9407 Los Gatos Blvd.</street>
<city>Los Gatos</city>
<state>CA</state>
<zip>95032</zip>
</Address>
</li>
<li>
<Address>
<street>4209 El Camino Real</street>
<city>Mountain VIEw</city>
<state>CA</state>
<zip>95033</zip>
</Address>
</li>
. . .
</ul>
使用轉換表示 XML 文檔中缺失的或重復的元素
我們來考慮前面提出的一個主題:如何編寫 XQuery 在返回的結果中表示缺失的值,以及表示一個 XML 文檔(比如一個客戶記錄)包含重復的元素(比如多個電子郵件地址)。一種方法涉及到將返回的輸出包裝在一個新的 XML 元素中,如以下查詢所示:
清單 31. 在 XQuery 結果中表示缺失的值或重復的元素
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt
where $y/Address[zip="95032"] or $y/Address[city="San Jose"]
return <emailList> {$y/email} </emailList>
運行這個查詢會返回一系列 emailList 元素,每個符合條件的客戶記錄都有一個 emailList 元素。每個 emailList 元素將包含電子郵件數據。如果 DB2 在一個客戶的記錄中只找到一個電子郵件地址,那麼它會返回這個元素及其值。如果找到多個電子郵件地址,就會返回所有 email 元素及其值。最後,如果沒有找到電子郵件地址,就會返回一個空的 emailList 元素。
對於我們的示例數據,這個查詢的輸出是:
清單 32. 前一個查詢的輸出
<emailList>
<email>[email protected]</email>
</emailList>
<emailList/>
<emailList/>
<emailList>
<email>[email protected]</email>
<email>[email protected]<emailList>
</emailList>
條件邏輯
可以使用幾個簡單的關鍵字將條件邏輯結合進 XQuery 中。
假設您要聯絡每位客戶。您最希望通過電子郵件與他們取得聯絡,但如果沒有他們的電子郵件地址,那麼就向他們家裡打電話。如果也沒有家庭電話號碼,就通過郵局郵寄一封信。因此,需要查詢 DB2 clIEnts 表,獲得一個包含每個客戶的單一電子郵件地址、家庭電話號碼或郵政地址的聯系列表。
如果將條件邏輯結合進 XQuery 中,這個任務就很容易完成。獲得所需信息的一種方法如下:
清單 33. 具有分三部分的條件表達式的 XQuery
xquery
for $y in db2-fn:XMLcolumn('CLIENTS.CONTACTINFO')/ClIEnt
return (
if ($y/email) then $y/email[1]
else if ($y/phone/home) then <homePhone>{$y/phone/home/text()}</homePhone>
else $y/Address)
我們來看看這個查詢的第四行到第六行。可以看到,它們是 return 子句的組成部分,因此決定查詢的輸出是什麼。
第四行檢查文檔中是否至少有一個 email 元素;如果有,那麼它指定應該返回第一個 email 元素。如果沒有 email 元素,那麼執行第五行。它指示 DB2 在 phone 元素下尋找 home 元素。如果文檔中包含家庭電話號碼,那麼提取它的文本節點並作為新的 “homePhone” 元素的一部分返回。最後,如果客戶的聯系文件(XML 文檔)中沒有電子郵件地址和家庭電話號碼,那麼 DB2 返回完整的 Address 元素。因為 clIEnts 表 中的所有記錄都包含郵政地址,所以這個查詢的邏輯確保 DB2 會為每個客戶返回一種聯系方式。
這個查詢的輸出是:
清單 34. 查詢輸出
<email>
[email protected]
</email>
<Address>
<street>
1204 Meridian Ave.
</street>
<apt>
4A
</apt>
<city>
San Jose
</city>
<state>
CA
</state>
<zip>
95124
</zip>
</Address>
<homePhone>
4083332222
</homePhone>
<email>
[email protected]
</email>
<email>
[email protected]
</email>
<homePhone>
6503310091
</homePhone>
“混合型” 查詢
您已經看到了如何編寫 XQuery 來檢索 XML 文檔片段、創建 XML 輸出的新形式以及根據在查詢本身指定的條件返回不同的輸出。這些都是查詢 DB2 中存儲的 XML 數據的簡單方法。
您要知道,關於 XQuery 要學習的內容遠比本教程討論的多得多。但是,有一個主題是我們不能忽略的:如何編寫同時包含 SQL 和 XQuery 表達式的查詢。如果需要讓查詢同時根據 XML 和非 XML 列值進行過濾,那麼就需要掌握這種技術。
因為本教程主要關注 XQuery 並將它用作頂級語言,所以我們先研究如何將 SQL 嵌入 XQuery 中。但一定要注意,也可以采用相反的做法 —— 將 XQuery 嵌入 SQL 中。在本教程末尾,您會看到這種做法的一個簡短示例。但是,關於 SQL/XML 以及如何將 XQuery 嵌入 SQL 中的更多信息,請參見 參考資料。
將 SQL 嵌入 XQuery 中
要將 SQL 嵌入 XQuery 中,需要使用 db2-fn:sqlquery 函數替代 db2-fn:xmlcolumn 函數。db2-fn:sqlquery 函數執行一個 SQL 查詢並只返回選擇的數據。傳遞給 db2-fn:sqlquery 的 SQL 查詢必須只返回 XML 數據;這使 XQuery 能夠進一步處理 SQL 查詢的結果。
我們用一個示例將已經討論的許多概念結合起來。假設希望獲得住在 San Jose 的 Gold 客戶的所有電子郵件地址。如果一個客戶有多個電子郵件地址,那麼希望這些地址在輸出中是同一個記錄的組成部分。最後,如果符合條件的 Gold 客戶沒有提供電子郵件地址,那麼希望檢索他的郵政地址。編寫這樣的查詢的一種方法如下:
清單 35. 將 SQL 嵌入包含條件邏輯的 XQuery 中
xquery
for $y in
db2-fn:sqlquery('select contactinfo from clients where status=''Gold'' ')/ClIEnt
where $y/Address/city="San Jose"
return (
if ($y/email) then <emailList>{$y/email}</emailList>
else $y/Address
)
我們仔細看看第三行,這裡嵌入了一個 SQL 語句。這個 SELECT 語句包含一個基於 status 列的查詢謂詞,將這個 VARCHAR 列的值與字符串 Gold 進行比較。在 SQL 中,這樣的字符串要包圍在單引號中。盡管這個示例看起來似乎使用了雙引號,但實際上是在比較值前後使用了兩個單引號(''Gold'')。“額外的” 單引號是轉義字符。如果在基於字符串的查詢謂詞外使用雙引號代替單引號,就會出現語法錯誤。
這個查詢的輸出是:
清單 36. 查詢輸出
<emailList>
<email>
[email protected]
</email>
<Address>
<street>
1204 Meridian Ave.
</street>
<apt> 4A
</apt>
<city>
San Jose
</city>
<state>
CA
</state>
<zip>
95124
</zip>
</Address>
將 XQuery 嵌入 SQL 中
一定要注意,也可以將 XQuery 嵌入 SQL 中。實際上,DB2 9 提供了對標准 SQL/XML 函數的支持,這些函數常常用來構造以 SQL 為最外層(即頂級)語言的混合型查詢。盡管詳細討論 DB2 的 SQL/XML 支持超出了本教程的范圍,但至少有兩個函數值得注意:
XMLExists 允許導航到 XML 文檔中的一個元素(或其他類型的節點)並對特定的條件進行測試。在 SQL WHERE 子句中指定時,XMLExists 將返回的結果限制為那些包含滿足指定條件(即指定的條件為 “true”)的 XML 文檔的行。您可能猜到了,應該將 XQuery 表達式作為輸入傳遞給 XMLExists 函數,從而導航到文檔中感興趣的節點。 XMLQuery 允許將 XML 放到 SQL/XML 查詢返回的 SQL 結果集中。它通常用來從 XML 文檔中檢索一個或多個元素。同樣,XMLQuery 函數也以 XQuery 表達式作為輸入。考慮以下查詢。這個查詢將 SQL 用作頂級語言並包含對 XMLQuery 和 XMLExists 函數的調用:
清單 37. 將 XQuery 路徑表達式嵌入 SQL 來獲取和限制 XML 數據
select name, status,
XMLquery('$c/ClIEnt/phone/home' passing contactinfo as "c")
from clIEnts
where XMLexists('$y/ClIEnt/Address[zip="95032"]' passing contactinfo as "y")
這個查詢返回的結果集包括客戶姓名、狀態和家庭電話號碼列。前兩列包含 SQL VARCHAR 數據,第三列包含從符合條件的 XML 文檔中提取的一個 XML 元素。我們來仔細看看這個查詢的第二行和第四行。
第二行在 SELECT 子句中調用 XMLQuery,這表示這個函數返回的結果應該作為 SQL 結果集中的一列。我們指定一個 XQuery 路徑表達式作為這個函數的輸入,這個表達式讓 DB2 導航到 ClIEnt 元素下面的 phone 元素的 home 子元素。這個路徑表達式引用一個變量($c),這個變量引用 contactinfo 列。
第四行在 SQL WHERE 子句中調用 XMLExists,這表示 DB2 應該根據一個 XML 謂詞對結果進行限制。這個謂詞是在路徑表達式中指定的,它表示我們只對具有特定郵政編碼的客戶感興趣。同樣,作為這個 SQL/XML 函數的輸入提供的路徑表達式定義一個變量(在這個示例中是 $y),這個變量標識 DB2 將在哪裡尋找 XML 文檔(在 contactinfo 列中)。
對於 示例數據,這個查詢的輸出是一個單行的結果集,其值如下:
清單 38. 查詢輸出
Lisa Hansen Silver <home>4083332222</home>
結束語
本教程講解了 DB2 對 XQuery 的支持,並解釋了幾個基本的語言概念。您學習了如何編寫和執行針對 DB2 中存儲的 XML 數據的簡單 XQuery。
本系列其他教程推薦
DB2 9 基礎(730 考試)認證指南,第 1 部分: DB2 規劃 1
DB2 9 基礎(730 考試)認證指南,第 1 部分: DB2 規劃 2
DB2 9 基礎(730 考試)認證指南,第 2 部分: 安全性
DB2 9 基礎(730 考試)認證指南,第 3 部分: 訪問 DB2 數據
DB2 9 基礎(730 考試)認證指南,第 4 部分: 處理 DB2 數據
DB2 9 基礎(730 考試)認證指南,第 5 部分: 處理 DB2 對象
DB2 9 基礎(730 考試)認證指南,第 6 部分: 數據並發性