在图 2 中可以看到,新文档视图 /app/views/documents/new.rhtml 显示创建了一个与 XML 内容相关联的新文档,还创建了与新文档相关联的新主题。
图 2. 将 XML 文档上载到 Team Room 存储库中
现在应该上载其他 XML 文档,这些文档包含加拿大和美国一些地区的模拟市场营销数据,后面将用这些文档运行 XQuery 和 XPath 搜索。这些 XML 文件放在 /test/fixtures 目录中,这里还放着用于文档检验的 marketinfo.xsd XML 模式。在本文末尾的 下载 中可以找到更新后的 Team Room 应用程序。
XML 数据类型的基本 CRUD 操作
我们来执行一些基本的 XML 创建、获取、更新和删除(CRUD)操作,体会一下如何管理 XML 数据。
为了演示这些操作,假设市场营销部门希望调查客户生活的城市。在这些城市中将发起一次有针对性的市场营销活动,从而提高品牌知名度和客户光顾次数。为此,我们可以从 XML_CONTENTS 表中存储的市场营销信息 XML 文档中提取出数据。另外,我们决定存储产生的调查报告,以便记录不断增加的城市信息库。如果决定以 XML 格式创建城市数据库,那么不需要创建另一个表来跟踪这些数据。我们只需用这些数据组成一个 XML 文档,然后将它作为 XML 插入同一列中。以后的某个时候,DBA 可能希望创建一个新的表来分隔应用程序数据,但这仅仅是一个逻辑和语义需求,不是数据库的要求。
在 DB2 中,可以以多种方式查询 XML 数据:使用 SQL、XQuery 或这两者的组合。这个示例使用 XQuery。使用 XQuery 有助于用查询结果构造 XML 文档。清单 11 给出要执行的 XQuery:
清单 11. 城市调查 XQuery
| XQUERY <cities> declare default element namespace "http://www.ibm.com/developerworks"; { for $c in fn:distinct-values( db2-fn:xmlcolumn( 'XML_CONTENTS.DATA')/marketinfo/sales/customer/address/city) order by $c return <city>{$c}</city> } </cities> |
首先看看函数 db2-fn:xmlcolumn() 中的语句,然后向外依次解释,这样才能理解这个 XQuery 的工作方式。db2-fn:xmlcolumn() 函数从当前连接的 DB2 数据库中的 XML 列获取一个序列。在这里,我们要从 XML_CONTENTS 表的 DATA 列获取数据。但是,我们不需要所有数据,只需要 XPath 表达式指定的子集:/marketinfo/sales/customer/address/city。
换句话说,我们希望查看表中每一行中的所有 XML 文档,并选择 XPath 中出现的所有 city 元素。这会造成一个潜在的问题,因为多个客户可能住在同一城市。为了处理这个问题,我们使用 XQuery 函数 fn:distinct-values()。顾名思义,它将返回一系列不同的 city 元素,城市名不会重复。这个序列被赋值给变量 $c。
在最后一步之前,对 $c 中的城市进行排序。然后返回结果。XQuery 的强大特性之一是,返回数据的格式是可以充分定制的。因为我们当前已经获得了一系列城市名称,所以将每个城市封装在一个
清单 12. 返回的典型 XML 数据
| <cities> <city>Atlanta</city> <city>Augusta</city> <city>Austin</city> <city>Baton Rouge</city> <city> ... </city> </cities> |
现在已经获得了 XML 文档形式的城市列表,我们要将这个文档插入数据库中。开发人员可以按照与插入任何数据类型相同的方式将 XML 文档插入数据库中。
清单 13. 插入 XML 文档
| class DocumentsController < ApplicationController [...] def upload [...] @document = Document.new(params[:document]) @subject = params[:subject_name] && params[:subject_name].empty? ? Subject.new : Subject.find_by_name(params[:subject_name]) Document.transaction do User.find(session[:user_id]).documents << @document @subject.documents << @document @subject.size = @subject.documents.size if @subject.new_record? @subject.name = params[:subject][:name] @subject.tag = params[:subject][:tag] @subject.description = params[:subject][:description] @subject.save end if @document.save flash[:notice] = "Document #{@document.name} successfully created." [...] end |
因为在任何给定的时刻客户的数量都不是静态的,所以我们希望定期更新客户城市的列表,比如每周一次或每月一次,从而保证这个列表反映最新情况。为此,可以执行相同的 XQuery 语句。数据库中当前的文档会被替换为 XML 文件的最新版本,因为它的 id 是相同的。注意,有一个传递给编辑视图表单(/app/views/documents/_form.rhtml)的隐藏的 :id 参数,在 DocumentsController 中使用这个参数获取现有的文档:
清单 14. 更新 XML 文档
| class DocumentsController < ApplicationController [...] def update @document = Document.find(params[:document][:id]) if @document.update_attributes(params[:document]) flash[:notice] = 'Document was successfully updated.' redirect_to :action => 'show', :id => @document else render :action => 'edit' end end [...] end |
在内部,DB2 重新分配现有的 XML 数据页面并插入新的值。所以实际效果就是用新的更新后的文档替换整个 XML 文档。
最后,当数据不再有用时,或者数据可以由其他机制处理时,就需要删除数据。例如,DBA 可能希望把不同的信息隔离开,比如使用另一个表中的另一个 XML 列。为了删除文档,需要执行 DELETE 语句,并加上适当的 WHERE 谓词。在 Ruby 中,可以这样做:
清单 15. 删除 XML 文档
| class DocumentsController < ApplicationController [...] def destroy Document.find(params[:id]).destroy redirect_to :action => 'list' end end |