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>anyemail@yahoo.com</email> <email>anotheremail@yahoo.com</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 元素
如果文档包含多个 email 元素,而您只想获得第一个,那么可以编写:
清单 3. 导航到第一个 email 元素
除了在路径表达式中指定元素节点之外,还可以使用 @ 符号指定属性节点,从而在元素中识别出属性。下面这个路径表达式导航到 id 属性等于 123 的 Client 元素中的第一个 email 元素:
清单 4. 指定属性节点和值
/Client[@id='123']/email[1] |
前面这个示例使用了一个基于属性值的过滤谓词。还可以根据其他节点值进行过滤。XPath 用户常常根据元素值进行过滤,比如下面的表达式返回住在加利福尼亚的客户的 zip 元素:
清单 5. 根据元素值进行过滤
/Client/Address[state="CA"]/zip |
可以使用通配符(“*”)匹配路径表达式中各个步上的任何节点。下面的示例获取在 Client 元素的任何直接子元素下找到的任何 city 元素。
清单 6. 使用通配符
对于我们的示例文档,这将返回值为 San Jose 的 city 元素。导航到这个 city 元素的更精确的方法是:
清单 7. 导航到 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 个值的序列:
let 有时候人们难以判断什么时候应该使用 let 关键字而不是 for。let 关键字并不像 for 关键字那样遍历一系列输入,并将每个条目依次绑定到一个变量,而是将一个单一输入值赋值给变量,但是这个输入值可以是零个、一个或更多条目的序列。因此,在 XQuery 中 for 和 let 的行为差异很大。
看一个示例应该有助于澄清这一差异。请考虑下面这个使用 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>love2shop@yahoo.com</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>golfer12@yahoo.com</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>beatlesfan36@hotmail.com</email> <email>lennonfan36@hotmail.com</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 在显示的 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> love2shop@yahoo.com </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> golfer12@yahoo.com </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> beatlesfan36@hotmail.com </email> <email> lennonfan36@hotmail.com </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. 前两个查询的示例输出
本节中前面的查询的结果相当简单,因为它们都涉及 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> love2shop@yahoo.com </email> <email> beatlesfan36@hotmail.com </email> <email> lennonfan36@hotmail.com </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> love2shop@yahoo.com </email> <email> beatlesfan36@hotmail.com </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>love2shop@yahoo.com</email> </emailList> <emailList/> <emailList/> <emailList> <email>beatlesfan36@hotmail.com</email> <email>lennonfan36@hotmail.com<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> love2shop@yahoo.com </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> golfer12@yahoo.com </email> <email> beatlesfan36@hotmail.com </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> love2shop@yahoo.com </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 部分: 数据并发性