清单 13. 基于 XMLTable 的输出创建视图
create view commentview(itemID, itemname, commentID, message, mustrespond) as
select i.id, i.itemname, t.CommentID, t.Message, t.ResponseRequested from items i,
xmltable('$c/Comments/Comment' passing i.comments as "c"
columns CommentID integer path 'CommentID',
Message varchar(100) path 'Message',
ResponseRequested varchar(100) path 'ResponseRequested') as t;
|
虽然在 XML 列中的数据上创建关系视图很容易,但是用起来要小心。在对那样的视图发出查询时,DB2 不使用 XML 列索引。因此,如果以 ResponseRequested 列为索引,并发出一条将 "mustrespond" 列的结果限制为某个特定值的 SQL 查询,那么 DB2 将读取所有的 XML 文档,并搜索适当的 "ResponseRequested" 值。除非数据量不大,否则这样做会降低运行时性能。然而,如果在那些视图上运行的查询还包含有严格限制性的谓词,且参与索引的项中有传统的 SQL 类型的项(在这个例子中可以是 "i.id" 或 "i.itemname"),那么可以缓解潜在的运行时性能问题。DB2 使用关系索引将符合条件的行过滤到一个较小的量,然后在返回最终结果之前,将更多的 XML 查询谓词应用到这些临时的结果上。
现在,您可能想知道如何连接 XML 数据和非 XML 数据(例如基于传统 SQL 类型的关系数据)。DB2 使您可以仅用一条 SQL/XML 语句来做到这一点。有很多方法可用来制定那样的连接,这取决于数据库模式和工作负载需求,不过这里我们只谈论一个例子。您也许会感到惊讶,其实您已经知道足够多关于 SQL/XML 的东西,完全可以实现这种连接。
还记得吗,"items" 表中的 XML 列包含一个 "CustomerID" 元素。这可以作为与 "clients" 表中基于整数的列 "id" 的一个连接键。因此,如果要获得一个报告,其中列出对您的一件或多件产品发表了评论的客户的姓名和状态,那么需要将一个表中的 XML 元素值与来自另一个表中的 SQL 整数值相连接。实现这一点的一种方法是使用 XMLExists 函数,如 清单 14 所示:
select clients.name, clients.status from items, clients
where xmlexists('$c/Comments/Comment[CustomerID=$p]'
passing items.comments as "c", clients.id as "p")
|
第一行标识出要包括在查询结果集中的 SQL 列以及查询中所引用的源表。第二行包括了连接子句。这里,
XMLExists 决定在一个目标源中的 "CustomerID" 值是否等于来自另一个目标源的值。第三行指定这两个源:第一个目标源是 "items" 表中的 XML 列 "comments",第二个目标源是 "clients" 表中的整数列 "id"。因此,如果客户对任何产品发表了评论,并且 "clients" 表中存在关于该客户的信息,那么 XMLExists 表达式将等于 "true",报告中将包括该客户的姓名和状态。
虽然我们只讨论了几个函数,其实 SQL/XML 为查询 XML 数据和将 XML 数据与关系数据集成提供了很多强大的功能。实际上,您已经看到了这方面的一些例子,但是这里我们还要再讨论一些例子。
通过 XMLExists 和 XMLQuery 函数都可以将 XQuery 嵌入到 SQL 中。前面的例子展示了如何使用这些函数和简单的 XPath 表达式访问 XML 文档中感兴趣的某个部分。现在我们考虑一个简单的例子,这个例子将 XQuery 包括在 SQL 查询中。
XQueries 可以包含 "for"、"let"、"where" "、"order by" 和 "return" 子句中的一些或者全部。这些子句一起形成了 FLWOR (发音为 flower)表达式。SQL 程序员会发现,将 XQueries 嵌入到 SELECT 列表中以便将 XML 文档的片段提取(或投影)到结果集是很方便的。虽然 XMLQuery 函数的用法不止于此,不过本文只讨论这种情况。(将来的文章将更深入地讨论 XQuery。)
假设您要检索 "Gold" 客户的姓名和首要 email 地址。在某些方面,这个任务类似于我们前面在探索如何投影 XML 元素值的时候完成过的一个任务(参见 清单 9)。而在这里,我们将 XQuery (带有 "for" 和 "return" 子句)作为 XMLQuery 函数的输入:
清单 15. 使用 XQuery 的 "for" 和 "return" 检索 XML 数据
select name, xmlquery('for $e in $c/Client/email[1] return $e'
passing contactinfo as "c")
from clients
where status = 'Gold'
|
第一行指定结果集中将包括客户姓名和
XMLQuery 函数的输出。第二行表明将返回 "Client" 元素的第一个 "email" 子元素。第三行标识出 XML 数据的源 —— "contactinfo" 列。第四行说明这个列在 "clients" 表中,最后,第五行表明我们只对 "Gold" 客户感兴趣。
因为这个例子很简单,在这里您可以这样编写这个查询。不过,也可以用一种更紧凑的方式编写这个查询:
select name, xmlquery('$c/Client/email[1]'
passing contactinfo as "c")
from clients
where status = 'Gold'
|
不过,通过 XQuery 的
return 子句可以按照需要转换 XML 输出。例如,您可以提取 email 元素值并将它们发布为 HTML。下面的查询将产生一个结果集,其中每个 Gold 客户的第一个 email 地址以 HTML 段落的形式返回。
select xmlquery('for $e in $c/Client/email[1]/text()
return <p>{$e}</p>'
passing contactinfo as "c")
from clients
where status = 'Gold'
|
第一行表明您只对符合条件的客户的第一个 email 地址的文本表示形式感兴趣。第二行指定该信息在返回之前需要用 HTML 段落标记括起来。具体来说,花括号({ })指示 DB2 计算被括起来的表达式(在这里是 "$e")的值,而不是将其视作一个文字字符串。如果省略了花括号,对于每个符合条件的客户记录,DB2 将返回一个包含 "<p>$e</p>" 的结果。
到到目前为止,我们一直都在着重讨论查询、提取或转换存储在 DB2 XML 列中的数据的方法。而且您已经看到,这些功能都可以通过 SQL/XML 提供。
SQL/XML 还提供了其他非常方便的特性。其中一个特性是将关系数据转换或发布为 XML。本文只讨论这方面的三个 SQL/XML 函数:XMLElement、XMLAgg 和 XMLForest。
通过 XMLElement 可以将存储在传统的 SQL 列中的数据转换成 XML 片段。也就是说,可以基于基本的 SQL 数据构造 XML 元素(带 XML 属性或者不带 XML 属性)。下面的例子嵌入了 XMLElement 函数来创建一系列的 item 元素,每个 item 元素包含一些子元素,分别存放从 "items" 表获得的 ID、品牌和库存单位("sku")值:
清单 18. 使用 XMLElement 将关系数据发布为 XML
select xmlelement (name "item", xmlelement (name "id", id), xmlelement (name "brand", brandname), xmlelement (name "sku", sku) ) from items where srp < 100 |
运行该查询将产生类似以下的结果:
<item> <id>4272</id> <brand>Classy</brand> <sku>981140</sku> </item> . . . <item> <id>1193</id> <brand>Natural</brand> <sku>557813</sku> </item> |
可以将
XMLElement 与其他 SQL/XML 发布函数结合使用来构造 XML 值以及将这些值分组,使它们嵌套成一定的层次结构。清单 20 中的例子使用 XMLElement 创建 customerList 元素,该元素的内容按照 "status" 列中的值分组。对于每个 "customerList" 记录,XMLAgg 函数返回一系列的 customer 元素,每个 customer 元素包含基于 "name" 和 "status" 列的子元素。而且可以看到,customer 元素的值是按照客户姓名排序的。
select xmlelement(name "customerList", xmlagg (xmlelement (name "customer", xmlforest (name as "fullName", status as "status") ) order by name ) ) from clients group by status |
假设 "clients" 表包含三个不同的 "status" 值:"Gold"、"Silver" 和 "Standard"。运行上述查询将导致 DB2 返回三个 customerList 元素,每个 customerList 元素可能包含多个 customer 子元素,每个 customer 子元素又进一步包含姓名和状态信息。因此,输出将类似于以下内容:
<customerList>
<customer>
<fullName>Chris Bontempo</fullname>
<status>Gold</status>
</customer>
<customer>
<fullName>Ella Kimpton</fullName>
<status>Gold</status>
</customer>
. . .
</customerList>
<customerList>
<customer>
<fullName>Lisa Hansen</fullName>
<status>Silver</status>
</customer>
. . .
</customerList>
<customerList>
<customer>
<fullName>Rita Gomez</fullName>
<status>Standard</status>
</customer>
. . .
</customerList>
|
更新和删除操作
虽然本文的重点是使用 SQL 搜索和检索存储在 XML 列中的数据,不过这里仍然值得花一点时间考虑一下另外两项常见的任务:更新和删除 XML 列中的数据。
DB2 允许用户使用 SQL 和SQL/XML 语句更新和删除 XML 数据。实际上,由于 XQuery 标准的初稿没有解决这些问题,DB2 用户必须依赖 SQL 来完成这些任务。
DB2 允许用 SQL UPDATE 语句或通过使用系统提供的存储过程(DB2XMLFUNCTIONS.XMLUPDATE)来更新 XML 列。不管使用哪种方式,对 XML 列的更新都发生在元素级。然而,使用存储过程更新 XML 数据的程序员不需要提供整个 XML 文档给 DB2;他们只需指定要更新的 XML 元素。发出 UPDATE 语句的程序员则需要指定整个文档(而不仅仅是要更改的元素)。
例如,如果要发出一条 UPDATE 语句来更改某个特定客户的联系方式信息中的 email 地址,就必须在 XML 列中提供全部联系方式信息,而不仅仅是新的 email 元素值。根据 图 2,提供的信息将包括 "Address" 信息、"phone" 信息、"fax" 信息和 "email" 信息。
考虑以下语句:
update clients set contactinfo=( xmlparse(document '<email>newemail@someplace.com</email>' ) ) where id = 3227 |
回忆一下在 “DB2 Viper 快速入门” 中我们是如何插入 XML 数据的,这里的语句大部分仍然是类似的。与任何 SQL
UPDATE 语句一样,这个例子首先标识出要更新的表和列。由于目标列包含 XML 数据,因此需要提供一个格式良好的 XML 文档作为新的目标值。虽然大多数生产环境在应用程序中使用主机变量或参数标记位来更新 XML 数据,但是在这里我展示了用一种简单的方式来交互式地完成该任务。第二行使用 XMLParse 函数将输入字符串转换成 XML。对于 beta 版的 Viper,需要显式地调用 XMLParse。当 Viper 变得普遍可用时,显式调用应该只是成为一种选择。最后一行是一个标准的 SQL 子句,规定只更新表中特定的一行。
如果执行上述 UPDATE 语句,则客户 3227 的 "contactinfo" 列将只包含 email 信息,如 清单 23 所示:
<email>newemail@someplace.com</email> |
这位客户的地址、电话号码和传真号码(如 图 2 所示)将丢失。而且,之前编写的用于提取客户的 email 地址的那些查询也无法恢复这些信息。为什么?之前的那些查询包括 XPath 或 XQuery 表达式,这些表达式在一个特定的文档层次结构中导航,而在这个结构中 Client 是根元素,email 是一个子元素。在像上面这样更新该文档之后,email 将变成这个客户的 XML 记录的根元素;因此,在这个层次结构中再也不能在预期的位置上找到它的值。
如果要交互式地更新这个客户的 email 地址,并且保留所有其他已有的联系方式信息,应该像 清单 24 中那样重写查询:
update clients set contactinfo= (xmlparse(document '‘<Client> <Address> <street>5401 Julio Ave.</street> <city>San Jose</city> <state>CA</state> <zip>95116</zip> </Address> <phone> <work>4084633000</work> <home>4081111111</home> <cell>4082222222</cell> </phone> <fax>4087776666</fax> <email>newemail@someplace.com</email> </Client>' ) ) where id = 3227 |
也许您想知道是否可以通过一个视图进行更新,从而避免提供整个 XML 文档。例如,清单 13 中定义的 commentview 使用
XMLTable 函数提取 XML 文档中的某些元素,并将这些元素转换成视图中的 SQL 列。那么,是否可以更新这些 SQL 列中某个列的值,并将结果写回到初始的 XML 文档的适当子元素中呢?答案是否定的。在 DB2 中,基于 SQL 类型的视图列与从一个函数(在这里是 XMLTable 函数)得到的视图列是有区别的。对后者的更新不受支持。
删除包含 XML 列的行很简单。SQL DELETE 语句允许通过 WHERE 子句识别(或限制)要删除的行。该子句可以包括简单的谓词来标识非 XML 列值或包括 SQL/XML 函数来标识包含在 XML 列中的 XML 元素值。
例如,下面展示了如何删除客户 ID 为 3227 的客户的所有信息:
delete from clients where id = 3227 |
还记得怎样限制 SQL SELECT 语句,使之仅返回居住在邮政编码为 95116 的地区的客户的行吗?如果还记得的话,很容易知道如何删除与那些客户相关的行。下面看看如何使用 XMLExists 来做这件事:
delete from clients
where xmlexists('$c/Client/Address[zip="95116"]'
passing clients.contactinfo as "c");
|
最后,值得注意的是,您可以创建专门的 XML 索引来加快对 XML 列中的数据的访问。由于本文是介绍性的文章,并且示例数据量比较少,所以本文不讨论这个话题。但是,在生产环境中,定义适当的索引对于取得最佳性能是非常重要的。本文的 参考资料 小节可以帮助您了解更多关于新的 DB2 索引技术的知识。
本文谈到了很多基础知识,提到了 SQL/XML 的几个关键方面,并展示了如何使用 SQL/XML 查询 XML 列中的数据。当然,除了这里讨论的用法外,用 SQL 和 SQL/XML 函数还可以做更多的事。本文给出了一个 简单的 Java 例子,这个例子解释了如何使用参数标记位和 SQL/XML 来查询 XML 列中的数据。在将来的文章中我们将更详细地讨论应用程序开发。但是,接下来的文章将探索 DB2 Viper 支持的一种新的查询语言,即 XQuery 的一些有趣的方面。
感谢 George Lapis、Matthias Nicola、Sriram Padmanabhan、Gary Robinson、Hardeep Singh 和 Bert Van der Linden 为本文提供的帮助。
RSS订阅






收 藏
推 荐