XML 是当今 Web 最常用的数据交换格式之一。DB2® 对 pureXML™ 的支持以及与 parsing and generating XML API(REXML)的结合,为 Web 应用程序开发提供了强大的技术组合。DB2 数据服务器中的本地 XML 支持引入了极大的灵活性,通过使用 DB2 的混合型数据库引擎编译器和解析器(可同时使用 SQL 和 XQuery),能够混合存储半结构化的具有层次结构的 XML 文档和关系数据。在 Ruby on Rails 系列 的第二篇文章中,我们将演示如何在第一篇文章的 Team Room 示例中利用 pureXML 特性。
在 Ruby on Rails 系列的第 1 部分中,我们使用 Ruby on Rails 和 DB2 构建了一个 Team Room 示例,这个应用程序允许注册的成员共享各种文本文档、图像文件和 XML 文档。为了管理不断增加的共享文档集合,您学习了如何按类别对文档分组。然后,学习了如何添加订阅特性,这样当用户订阅的文档类别中添加了新文档时,我们就可以通过电子邮件通知他们。在第一篇文章的末尾,成员能够将不同类型的文件上载到 Team Room,文件被存储在后端 DB2 数据服务器中。现在,我们进一步增强 Team Room,提供更高级的用户特性和更好的资源访问。
首先,在用户模型和它的底层表持久存储中增加一些必要的东西,从而支持正确的身份验证:一个惟一的用户 id 字符串和一个散列的密码(使用 SHA 算法和一个伪随机种子)。可以添加其他用户属性(比如有效性、等级等等)来进一步改进这个用户模型,并对控制器和视图做几处修改来支持新用户的注册和安全登录。我们的 Rails 项目放在 D:\rails\teamroom 目录中,所以下面引用的所有路径都是 D:\rails\teamroom 目录中的相对路径。
a) 执行 ruby script/generate migration add_user_credentials_columns 启动迁移进程,在 USERS 表中添加必要的列。
b) 编辑 db/migrate/008_add_user_credentials_columns.rb 文件,添加必要的列(见清单 1):
class AddUserCredentialsColumns < ActiveRecord::Migration def self.up add_column :users, :userid, :string, :limit => 8 add_column :users, :hash_passwd, :string add_column :users, :salt, :string end def self.down remove_column :users, :userid remove_column :users, :hash_passwd remove_column :users, :salt end end |
c) 运行 rake db:migrate,在 USERS 表中添加这些新列。
在第一篇文章描述的 Team Room 中,每个主题只能属于一个订阅。每个订阅是一个用户已经订阅的主题集合,这让我们的 Team Room 相当不真实,因为一个用户选择了某个主题之后,Team Room 中的其他用户就不能再订阅这个主题了。
随着 Team Room 的流行,许多成员希望订阅同一个主题。成员的这种要求是正常的,应该允许对同一主题进行多次订阅。在更新的 Team Room 中,一个用户可以通过一个订阅订阅许多主题。为了实现这一修改,需要执行以下步骤:
SUBJECTS_SUBSCRIPTIONS 表包含以下列:
列名 | 数据类型 | 描述 |
---|---|---|
SUBSCRIPTION_ID | Integer | SUBSCRIPTIONS 表的外部 ID |
SUBJECT_ID | Integer | SUBJECTS 表的外部 ID |
在迁移过程中,执行以下步骤:
a) 执行 ruby script/generate migration create_subjects_subscriptions_table。
b) 编辑 db/migrate/009_create_subjects_subscriptions_table.rb 文件:
class CreateSubjectsSubscriptions < ActiveRecord::Migration def self.up create_table :subjects_subscriptions, :id => false do |t| t.column :subscription_id, :integer, :null => false t.column :subject_id, :integer, :null => false end remove_column :subjects, :subscription_id add_index :subjects_subscriptions, :subject_id end def self.down drop_table :subjects_subscriptions add_column :subjects, :subscription_id, :integer end end |
c) 运行 rake db:migrate 创建 SUBJECTS_SUBSCRIPTIONS 表。
e) 将 /app/models/subject.rb 文件中现有的关联 belongs_to :subscription 替换为新关联 has_and_belongs_to_many :subscriptions。
f) 将 /app/models/subscription.rb 文件中现有的关联 has_many: subject 替换为 has_and_belongs_to_many :subjects。
我们的市场营销部门用 XML 格式收集了匿名的客户信息,用来分析客户的购物习惯。下面是零售商为市场研究收集的数据示例。
|
每个客户购买记录包含一个美国或加拿大的地址、产品类别细节(包括产品的 SKU 号)以及上一次购买的日期。产品类别包括:
后面一节演示如何使用 Team Room 应用程序对这个 XML 数据执行查询操作。
可以将一份文本文档格式的市场营销报告与上面的 XML 客户数据关联起来。市场营销报告可以包含市场分析结果或者关于数据收集方式的细节。
在第 1 部分中,我们创建了一个 XML 类型的列来存储收集的市场营销数据。为了高效地管理 XML 数据,像传统的 SQL 数据类型一样,DB2 在内部使用 XML 数据模型作为逻辑数据模型,同时 也作为物理存储的基本单元。另外,当指定 XML 数据类型时,会向数据库用户公开这个数据模型。尽管这为管理 XML 数据提供了强大的功能和灵活性,尤其适合以 XML 为中心的开发人员,但是当前对于能够在 XML 列上执行的传统数据库管理活动类型还有一些限制。例如,包含 XML 列的表无法由 DB2 进行物理重组,这是因为 XML 具有层次化的存储结构。这实际上意味着,在包含 XML 列的表中,不能用 ALTER 操作删除列。尽管未来的 DB2 版本中可能会取消这个限制,但是现在要记住这个限制,因为这是 Ruby on Rails 迁移的一个基本操作。
为了继续利用 Ruby on Rails 迁移的灵活性,同时保持 XML 的强大功能,我们要创建一个单独的表来存储 XML 数据。我们将这个表命名为 XML_CONTENTS。它将存储 XML 文档,DOCUMENTS 表仍然存储其他所有相关信息。以后可以在 DOCUMENTS 表中添加或删除列,而不会受到 XML_CONTENTS 表中 XML 数据的影响,也不会影响这些 XML 数据。
关于当前使用 XML 数据类型的限制的更多细节,请参考 IBM DB2 Database for Linux, UNIX, and Windows Information Center 中的 “” 部分。
为了执行这个任务,我们生成并运行以下迁移:
a) 运行 ruby script/generate migration create_xml_contents,这将创建 db/migrate/010_create_xml_contents.rb 文件。
b) 编辑 db/migrate/010_create_xml_contents.rb 文件:
class CreateXmlContents < ActiveRecord::Migration def self.up drop_table :documents create_table :documents do |t| t.column :name, :string, :null => false t.column :size, :integer, :null => false t.column :data, :binary, :limit => 2.megabytes t.column :content_type, :string, :null => false t.column :created_at, :timestamp t.column :updated_at, :timestamp t.column :platform, :string, :limit =>10 t.column :subject_id, :integer t.column :user_id, :integer end create_table :xml_contents do |t| t.column :name, :string t.column :data, :xml, :null => false t.column :document_id, :integer end end def self.down drop_table :documents drop_table :xml_contents create_table :documents do |t| t.column :name, :string, :null => false t.column :size, :integer, :null => false t.column :data, :binary, :limit => 2.megabytes t.column :content_type, :string, :null => false t.column :created_at, :timestamp t.column :updated_at, :timestamp t.column :platform, :string, :limit =>10 t.column :subject_id, :integer t.column :user_id, :integer t.column :xmldata, :xml, :null => false end end end |
c) 运行 rake db:migrate 以删除现有的 DOCUMENTS 表、创建只存储 XML 数据的新表 XML_CONTENTS 和新的 DOCUMENTS 表(不包含 XML 列)。
d) 在新的 DOCUMENTS 表和 XML_CONTENTS 表之间重新建立一个关系。
首先,在步骤 d 中生成的 /app/models/xml_content.rb 中添加 belongs_to :document 关联。
第二,在 /app/models/document.rb 文件中添加 has_one :xml_content 关联。
e) 上载功能与以前的文档模型(document.rb,现在变成了父模型)中的实现相似,并添加创建子模型(xml_content)的操作。
NAME 列中存储原来的文件名。
self.name = File.basename(doc_field.original_filename).gsub(/[^\w._-]/, '') self.content_type = doc_field.content_type.chomp self.size = doc_field.size self.created_at = Time.now |
XML_CONTENTS.DATA 列存储指定的文件。
unless self.content_type.include?('text/xml') self.data = doc_field.read else content = XmlContent.new content.name = self.name content.data = doc_field.read self.xml_content = content end |
最终的 /app/models/document.rb 应该像清单 7 这样。
class Document < ActiveRecord::Base belongs_to :user belongs_to :subject has_one :xml_content # values displayed | stored PLATFORM_TYPES = [ ['Neutral', 'Any'], ['Windows', 'WinXP'], ['Mac OS X', 'MacOS'], ['Linux', 'Linux']] def uploaded_doc=(doc_field) self.name = File.basename(doc_field.original_filename).gsub(/[^\w._-]/, '') self.content_type = doc_field.content_type.chomp self.size = doc_field.size self.created_at = Time.now unless self.content_type.include?('text/xml') self.data = doc_field.read else content = XmlContent.new content.name = self.name content.data = doc_field.read self.xml_content = content end end end |
既然定义了 XML_CONTENTS 和 DOCUMENTS 表的模型,就必须修改视图来处理上载和显示功能。更新了上载功能和列出文档的视图,允许显式地选择要显示的模型属性(列)。
/app/views/documents/list.rhtml 实现这种显式的列选择:
|
然后,对控制器做以下更新来处理 XML 数据的文档显示功能。这些代码添加在 /app/controllers/documents_controller.rb 中。
def show @document = Document.find(params[:id]) doc_type = @document.content_type unless doc_type.include?('text/xml') doc_content = @document.data else doc_content = @document.xml_content.data end send_data(doc_content, :filename => @document.name, :type => doc_type, :disposition => "inline") end |
还要做以下更新来支持 XML 文档上载,您可能注意到了主题和用户关系处理的文档实现。
def upload if params[:document][:uploaded_doc].to_s.empty? flash[:notice] = "Please provide a file for upload" redirect_to(:action => "new" ) else @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." if @document.subject.subscriptions SubscriptionMailer.deliver_notify(@document) end redirect_to :action => 'list' else render :action => 'new' end end end |
下面的 图 1 描述了在执行上面的迁移步骤之后,Team Room 中不同模型之间的关联。
既然已经通过迁移实现了所有必需的模式修改,就可以将 XML 数据上载到 XML_CONTENTS 表中。将多个 XML 文档插入表中最容易的方法是使用 DB2 9 数据服务器中的 IMPORT 实用程序。IMPORT 实用程序允许导入格式良好的 XML 文档,可以同时进行 XML 模式检验,也可以不检验。对于我们当前的使用情况,需要确保正确地更新 XML_CONTENTS 表、DOCUMENTS 表和 SUBJECTS 表中的相关条目。因此,应该使用生成的 scaffold 每次上载一个文件,从而维护所有外键关联。
在图 2 中可以看到,新文档视图 /app/views/documents/new.rhtml 显示创建了一个与 XML 内容相关联的新文档,还创建了与新文档相关联的新主题。
现在应该上载其他 XML 文档,这些文档包含加拿大和美国一些地区的模拟市场营销数据,后面将用这些文档运行 XQuery 和 XPath 搜索。这些 XML 文件放在 /test/fixtures 目录中,这里还放着用于文档检验的 marketinfo.xsd XML 模式。在本文末尾的 下载 中可以找到更新后的 Team Room 应用程序。
|
我们来执行一些基本的 XML 创建、获取、更新和删除(CRUD)操作,体会一下如何管理 XML 数据。
为了演示这些操作,假设市场营销部门希望调查客户生活的城市。在这些城市中将发起一次有针对性的市场营销活动,从而提高品牌知名度和客户光顾次数。为此,我们可以从 XML_CONTENTS 表中存储的市场营销信息 XML 文档中提取出数据。另外,我们决定存储产生的调查报告,以便记录不断增加的城市信息库。如果决定以 XML 格式创建城市数据库,那么不需要创建另一个表来跟踪这些数据。我们只需用这些数据组成一个 XML 文档,然后将它作为 XML 插入同一列中。以后的某个时候,DBA 可能希望创建一个新的表来分隔应用程序数据,但这仅仅是一个逻辑和语义需求,不是数据库的要求。
在 DB2 中,可以以多种方式查询 XML 数据:使用 SQL、XQuery 或这两者的组合。这个示例使用 XQuery。使用 XQuery 有助于用查询结果构造 XML 文档。清单 11 给出要执行的 XQuery:
XQUERY |
首先看看函数 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 的强大特性之一是,返回数据的格式是可以充分定制的。因为我们当前已经获得了一系列城市名称,所以将每个城市封装在一个
|
要想了解 DB2 9 中的 XQuery 的更多信息,请参考 DB2 XML Guide(本文末尾的 参考资料 一节中提供了有关链接)。
现在已经获得了 XML 文档形式的城市列表,我们要将这个文档插入数据库中。开发人员可以按照与插入任何数据类型相同的方式将 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 中使用这个参数获取现有的文档:
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 中,可以这样做:
class DocumentsController < ApplicationController [...] def destroy Document.find(params[:id]).destroy redirect_to :action => 'list' end end |
除了基本的 CRUD 操作之外,还可以利用 XQuery 语言的功能非常精确地控制如何执行查询以及如何返回数据。例如,尽管获得客户所在的城市的列表是有意义的,但是市场营销部门更希望知道哪些类别的产品最畅销。这可以使市场营销活动更有针对性,不必将市场营销活动的预算分散在所有产品上。度量产品是否畅销的一种方法是,检查最近的销售是否是在某一产品类别中发生的,或者在特定的时间段内这些类别的产品是否畅销。
为了进行数据挖掘,我们首先问一个问题:在 2007 年 4 月 15 日到 2007 年 4 月 30 日之间,客户购买了哪些类别的产品?
为了回答市场营销部门的这个问题,使用清单 16 所示的查询:
XQUERY declare default element namespace "http://www.ibm.com/developerworks"; |
与前面的示例相似,我们要返回一个有效的 XML 文档,所以将 XQuery 的结果封装在 XML 根元素
“let” 语句本身由一个 FLWOR(for、let、where、order by 和 return) XQuery 表达式组成。您可能会注意到,FLWOR 表达式常常相当于 SQL 中的 SELECT-FROM-WHERE 块。然后,循环遍历每个客户:for $c in db2-fn:xmlcolumn("XML_CONTENTS.DATA") /marketinfo/sales/customer,条件是购买日期晚于 2007 年 4 月 15 日,并早于 2007 年 4 月 30 日。如果购买满足这个条件,市场营销部门就想知道这次购买的情况:where xs:date($c/last_purchase) > xs:date("2007-04-15") and xs:date($c/last_purchase) <= xs:date("2007-04-30")。
对于这些购买,我们返回存储为 XML 属性的类别类型:$c/categories/category/@type。与前一个示例一样,使用 fn:distinct-values() 函数避免重复出现同一类别中的购买。将这个类别列表绑定到变量 $categories。
现在,$categories 存储着一系列不重复的类别,但是因为我们希望返回 XML,所以将序列中的每个值封装在
|
这个 XQuery 可以进行参数化,这样就能够在市场营销部门感兴趣的任何时间范围上执行查询。另外,为了封装这个查询,可以将该功能放在一个存储过程中,这样就可以为 Rails 开发人员提取 XQuery 细节。
在下面的图 3 中,文档列表视图(/app/views/documents/list.rhtml)布局还包含预构建的报告表单,可以用来执行 XQuery 和 XPath 搜索。
既然已经尝到了甜头儿,市场营销部门对查询数据的要求就越来越高了。他们想知道在一个特定地区(具有特定的邮政编码)某类产品售出了多少。邮政编码代表特定区域,该区域中可能售出了大量产品,或者有大量客户投诉。这种信息可以帮助市场营销人员和产品经理判断哪些产品在哪些销售区域畅销,并将产品与邮政编码相关联。市场营销部门可以考虑在一个区域中哪些产品应该停止销售,根据某地区的产品畅销度确定市场营销重点。
我们将使用 SQL/XML 函数 XMLQUERY() 实现这个查询。用 SQL/XML 进行查询可以结合这两种技术的长处。例如,可以使用 SQL/XML 执行以下操作:
我们将在 XQuery 中使用 (1)、(2) 和 (5) 项操作。为了简单,我们不对示例进行参数化,并假设我们感兴趣的是 “Jewelry” 产品类别在邮政编码为 “79081” 的地区的销售情况。但是,本文提供的应用程序代码(见本文末尾的 下载 一节)包含的查询允许任何类别或邮政编码的参数。
select name, xmlquery( 'declare default element namespace "http://www.ibm.com/developerworks"; let $total := sum ( for $i in $t//category let $sum := count($i/item) where $i/@type = "Jewelry" return $sum ) return |
这个 SELECT 语句对于每个地区返回一行,地区名由 name 列表示。另外,对于每一行,返回一个有效的 XML 文档,其中只包含一个根元素
在 SQL 级上执行的另一项处理是,我们不希望 XMLQUERY() 对列中的所有 XML 文档执行操作。我们希望将查询的范围限制在包含邮政编码为 “79081” 的客户的行。找到这些行之后,只对它们执行 XQuery。也就是查询这些行中的所有客户购买记录,无论其邮政编码是否为 “79081”。
这个过滤处理是在 SQL WHERE 子句中用 SQL/XML XMLEXISTS() 函数谓词实现的。XMLEXISTS 谓词判断一个 XQuery 表达式是否返回一个元素序列。在我们的示例中。传递给 XMLEXISTS() 的 XQuery 表达式是一个简单的 XPath 表达式,其中的 $t 是 XML 列 data:$t/marketinfo/sales/customer/address[zip = "79081"]。
这个 XPath 表达式可以解释为:返回邮政编码为 “79081” 的 address 元素的序列。如果指定的 XPath 返回一个空序列,那么 XMLEXISTS 返回 false。否则返回 true。既然 SQL 已经帮助我们获得了 XML 文档的子集,XQuery 的操作范围就确定了;XQuery 使用一个绑定到变量 $total 的 let。$total 被赋值为一个 FLWOR 表达式的结果。对于类别类型为 “Jewelry”(where $i/@type = "Jewelry")的 XML 文档中的任何
这返回每个客户的销售额。将结果传递给 sum() 函数,从而求出某个类别的所有客户的总销售额。现在获得了 $total。因为我们希望返回 XML,所以将总销售额封装在
要想了解在 DB2 9 中使用 XQuery 的更多信息,请参考 DB2 XML Guide(参考资料 一节中提供了这个文档的链接)。
请考虑一个场景:一个新构建的应用程序需要与不支持本地 XML 的遗留应用程序和存储库集成。XML 文档中存储的信息可能需要放在一个关系表对象中。
假设市场分析的结果需要存储在关系表中,供一个遗留应用程序进行分析。这就要求分解 XML 数据并将它插入关系表中。
pureXML 提供了许多直接操作 XML 数据的工具。如果需要的话,pureXML 也能够执行分解。DB2 9 数据服务器为执行 XML 分解提供了以下内置工具。
1. XDBDECOMPXML 存储过程可以使用 DB2 9 中的带注释的 XML 模式分解特性将 XML 文档分解到关系表。顾名思义,这个特性在 XML 模式中使用注释作为映射语言,将 XML 文档中的信息映射到关系表。它需要一种 XML 模式,这个 XML 模式文档必须存储在 DB2 XSR 中并表明用于分解。可以通过一个 DB2 存储过程(xdbDecompXML)调用将 XML 文档分解到映射的关系列,也可以使用一个 Command Line Processor(CLP)命令。
关于通过 Visual Studio Add-in 执行 XML 分解的细节,请参考 “Introduction to annotated XML schema decomposition using the DB2 Visual Studio 2005 Add-in”。
注释 XML 模式的另一种方法是使用 DB2 Developer Workbench(DWB)。DWB 可以免费下载,它为创建、编辑、调试、部署和测试 DB2 数据库应用程序提供了一个完整的环境,包括开发存储过程和用户定义的函数。在本文末尾的 参考资料 一节中提供了下载信息。
DWB 的组件之一是用于带注释的 XML 模式分解的 Mapping Editor。可以使用这个简单直观的图形界面在 XML 模式和关系模式之间建立映射。以图形化方式将 XML 元素或属性映射到 DB2 中的关系列,就会自动地在 XML 模式文档中加上注释。保存 XML 模式并在 XSR 中注册之后,就可以将 XML 文档分解到 DB2 数据库中。
对 XML 模式注释和 xdbDecompXML 存储过程的全面讨论超出了本文的范围。要想了解关于带注释的 XML 模式分解的更多信息,包括基于内容的有条件分解或指定在插入前应用的内容转换等高级特性,请参考 DB2 9 XML Guide(参见本文的 参考资料 一节)。要想进一步了解 XML Extender 以及它的分解方法,请参考 Mayank Pradhan 撰写的 developerWorks 文章 “From DAD to Annotated XML Schema Decomposition”。
2. XMLTABLE SQL 表函数XMLTABLE 是一个 SQL 表函数,它对一个 XML 文档执行 XQuery 表达式并返回一个表。返回的表可以包含任何 SQL 数据类型的列,包括 XML。通过结合使用 XMLTABLE 和 INSERT 语句(这常常被称为 “Insert-from-XMLTABLE” 语句),可以将从 XML 文档获取的值插入关系表中。这样就可以实现与带注释的 XML 模式分解相同的功能。
这提供了一种执行 XML 分解的简单方法,可以将 XML 文档的片段存储在关系表的各个列中。
下面的语句执行一个 XQuery 表达式,并以表的形式返回 DATA 列中的值。每一行显示与特定客户相关联的城市、州、邮政编码和最后购买日期。
SELECT X.CITY, X.STATE, X.ZIP, X.LAST_PURCHASE FROM TEAMROOM.XML_CONTENTS, XMLTABLE (XMLNAMESPACES (DEFAULT 'http://www.ibm.com/developerworks'), 'db2-fn:xmlcolumn("XML_CONTENTS.DATA")//customer' COLUMNS "CITY" CHAR(16) PATH './address/city', "STATE" CHAR(16) PATH './address/state', "ZIP" CHAR(6) PATH './address/zip', "LAST_PURCHASE" DATE PATH './last_purchase') as X |
以上 XQuery 的输出类似下面所示:
Baton Rouge LA 77888 03/10/2007 Baton Rouge LA 14257 01/07/2007 Richmond VA 78045 01/26/2007 Oklahoma City OK 71107 04/13/2007 Tallahassee FL 41720 04/25/2007 Richmond VA 39591 03/25/2007 Richmond VA 36522 03/23/2007 Richmond VA 32230 02/12/2007 Charleston WV 33015 02/12/2007 Columbia SC 72647 01/11/2007 Raleigh NC 11238 04/02/2007 Nashville TN 21245 01/06/2007 Fankfort KY 53793 04/18/2007 Austin TX 35462 03/13/2007 Columbia SC 68359 01/01/2007 Jackson MS 25770 01/20/2007 Little Rock AR 46342 03/10/2007 Tallahassee FL 54306 01/20/2007 Charleston WV 44339 02/20/2007 Frankfort KY 92403 02/27/2007 |
假设定义了具有正确定义的 SQL 类型的 CUSTOMER_INFOS 表,就可以用一个 INSERT 语句封装 SELECT 语句,从而将 XML 市场营销信息中提取出的客户数据(表格式)插入关系表:
INSERT INTO TEAMROOM.CUSTOMER_INFOS SELECT X.CITY, X.STATE, X.ZIP, X.LAST_PURCHASE FROM TEAMROOM.XML_CONTENTS, XMLTABLE (XMLNAMESPACES (DEFAULT 'http://www.ibm.com/developerworks'), 'db2-fn:xmlcolumn("XML_CONTENTS.DATA")//customer' COLUMNS "CITY" VARCHAR(16) PATH './address/city', "STATE" CHAR(16) PATH './address/state', "ZIP" CHAR(6) PATH './address/zip', "LAST_PURCHASE" DATE PATH './last_purchase') as X |
另外,Ruby 提供了一个称为 Ruby Electric XML(REXML)的 XML 处理器。可以使用它对 XML 数据进行树解析和流解析。
本系列的第 1 部分引入了订阅特性,让用户能够订阅感兴趣的主题。当订阅的主题出现更新时,用户会收到电子邮件通知。多么古老的方式,似乎还停留在 20 世纪。在这个 Web 2.0 时代,一些用户可能希望在阅读器中以 RSS 或 Atom 提要形式接收这些通知。
可以使用 DB2 9 pureXML 生成这样的提要,用户可以在提要阅读器中方便地查看这些。我们使用 SQL/XML XML 发布函数来实现这个特性。XML 发布函数用来构造 XML 节点和文档。关系数据和 XML 数据都是可以使用的。
注:发布函数有时候也称为构造器函数。
DB2 9 中的 XML 发布函数包括:
我们来提供一个 Atom 提要,当订阅的主题发生更新时,它会通知用户。尽管我们选用 Atom 提要,但是 DB2 同样能够生成 RSS 提要。这仅仅是选用哪种提要 XML 模式来构造提要的问题。
CREATE PROCEDURE GET_ATOM_FEED ( ) DYNAMIC RESULT SETS 1 ------------------------------------------------------------------------ -- SQL Stored Procedure ------------------------------------------------------------------------ P1: BEGIN -- Declare cursor DECLARE cursor1 CURSOR WITH RETURN FOR SELECT XMLSERIALIZE( XMLDOCUMENT( XMLELEMENT (NAME "feed", XMLNAMESPACES(DEFAULT ''), XMLCONCAT ( XMLELEMENT (NAME "id", 'http://localhost:3000/documents'), XMLELEMENT (NAME "title", 'Teamroom Documents'), XMLELEMENT (NAME "updated", CURRENT TIMESTAMP), XMLELEMENT (NAME "link", XMLATTRIBUTES('http://localhost:3000/documents/atom_feed' as "href", 'self' as "rel")), XMLELEMENT (NAME "author", XMLCONCAT( XMLELEMENT (NAME "name", 'TeamRoom'), XMLELEMENT (NAME "email", 'teamroom@developerWorks.ibm.com') ) ), XMLAGG ( XMLELEMENT (NAME "entry", XMLCONCAT ( XMLELEMENT (NAME "title", name), XMLELEMENT (NAME "id", 'http://localhost:3000/documents/show/' || CHAR(id)), XMLELEMENT (NAME "updated", updated_at), XMLELEMENT (NAME "link", 'http://localhost:3000/documents/show/' || CHAR(id)), XMLELEMENT (NAME "category", category), XMLELEMENT (NAME "summary", content_type), XMLELEMENT (NAME "content", XMLATTRIBUTES('text' as "type"), content) ) ) ) ) ) ) AS CLOB INCLUDING XMLDECLARATION ) FROM (SELECT d.id as id, d.name as name, d.content_type as content_type, d.updated_at as updated_at, s.name as category, s.description as content FROM DOCUMENTS d, SUBJECTS s WHERE d.subject_id = s.id ORDER BY d.updated_at DESC FETCH FIRST 10 ROWS ONLY) AS doc_list; -- Cursor left open for client application OPEN cursor1; END P1 |
我们选择以存储过程的形式提供这个功能,这样 Rails 开发人员就不需要为生成 Atom 提要的细节操心。开发人员只需调用这个存储过程即可。
class Document < ActiveRecord::Base [...] def atom_feed feed = Document.find_by_sql("call teamroom.get_atom_feed()") content = feed[0].attributes["1"] send_data(content, :filename => 'TeamRoomFeed.atom', :type => 'text/xml', :disposition => "inline") end [...] end |
对 DB2 XML 发布函数和 Atom Syndication Format Protocol 的全面讨论超出了本文的范围。更多的信息请参考 参考资料 一节中的链接。另外,本文提供的应用程序下载包包含 Atom 提要的完整实现,包括用来生成 feed 的存储过程和应用程序代码。
DB2 的 pureXML 功能可以将 XML 数据存储为其固有的层次化格式,这使应用程序能够轻松地使用 XML 语言,同时保持 DB2 关系数据库管理系统提供的性能、可伸缩性、可靠性和可用性优势。再加上 Ruby on Rails 框架的简单性和灵活性,开发人员就可以快速轻松地构建、部署和维护高质量的 Web 2.0 应用程序。这两种技术的组合非常灵活,可以根据业务的需要将信息从关系格式转换为层次化格式。在转换数据格式的同时,仍然能够保持出色的性能(因为使用了同样的优化技术),而且可以应用 DB2 提供的所有其他可靠性和可用性特性:比如备份、恢复和可伸缩性。
DB2 pureXML 不仅支持基本的本地 XML 支持,还可以以节点级粒度在硬盘上存储解析后的数据,从而支持通过编制索引(实际文档节点位置)来提高查询性能,以及使用 DB2 查询引擎中内置的 XQuery 和 XPath 原语。这使它非常适合在 Ruby on Rails 运行时中使用,因为 Ruby on Rails 提供了一组非常出色的 XML 库(例如 REXML 和 ROXML),可以快速地将 XML 数据存储与 Web 应用程序集成起来。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/9403012/viewspace-89/,如需转载,请注明出处,否则将追究法律责任。