在 Oracle 数据库 10g 第 2 版中,Oracle 引入了一个与该数据库集成的全功能自带 XQuery 引擎,该引擎可用于完成与开发支持 XML 的应用程序相关的各种任务。XQuery 是一种用于处理 XML 数据模型的查询语言,它实际上可操作任何类型的可用 XML 表达的数据。尽管 Oracle XQuery 实施使您可以使用数据库数据和外部数据源,但在处理数据库中存储的结构化数据方面,Oracle XML DB 通常可以显著提高性能。
本文提供的示例不仅演示了在什么场合下以及如何使用 XQuery 查询、构建和转换 XML,而且还演示了如何监控和分析 XQuery 表达式的性能执行,从而找到更高效的方法来处理同一工作负载。
基于关系数据构建 XML
在需要的情况下(例如,向 Web 服务发送结果),您可能要基于关系数据构建 XML。要在 Oracle 数据库 10g 第 2 版之前的版本中完成此任务,通常需要使用 SQL/XML 生成函数,如 XMLElement、XMLForest 和 XMLAgg()。在 Oracle 数据库 10 g 第 2 版中,XQuery 将比这些函数更为高效。具体而言,在 XQuery 表达式内部使用 ora:view XQuery 函数,您可以查询现有的关系表或视图以及即时构建 XML,从而不必通过关系数据显式创建 XML 视图。列表 1 中的 PL/SQL 代码演示了如何使用 ora:view 基于示例数据库模式 HR 的默认员工关系表中存储的数据构建 XML 文档。
列表 1:使用 ora:view 基于关系数据创建 XML
BEGIN IF(DBMS_XDB.CREATEFOLDER('/public/employees')) THEN DBMS_OUTPUT.PUT_LINE('Folder is created'); ELSE DBMS_OUTPUT.PUT_LINE('Cannot create folder'); END IF; COMMIT; END; /DECLARE XMLdoc XMLType; BEGIN SELECT XMLQuery( 'for $j in 1 return ( { for $i in ora:view("HR", "employees")/ROW where $i/EMPLOYEE_ID <= 102 return ( {xs:string($i/EMPLOYEE_ID)} {xs:string($i/LAST_NAME)} {xs:integer($i/SALARY)} )} )' RETURNING CONTENT) INTO XMLdoc FROM DUAL; IF(DBMS_XDB.CREATERESOURCE('/public/employees/employees.xml', XMLdoc)) THEN DBMS_OUTPUT.PUT_LINE('Resource is created'); ELSE DBMS_OUTPUT.PUT_LINE('Cannot create resource'); END IF; COMMIT; END; /
在列表 1 中的第一个 PL/SQL 过程中,您只是在 XML 信息库中创建了一个新文件夹。在该信息库文件夹中,您随后将存储此处显示的第二个 PL/SQL 过程中创建的 XML 文档。第二个 PL/SQL 过程首先发出 SELECT 语句,该语句使用 XMLQuery SQL 函数基于关系数据构建 XML。对于 XQuery 表达式(XMLQuery 在此处将其用作参数)而言,请注重嵌套的 FLWOR 表达街惺褂玫?ora:view XQuery 函数。在该示例中,ora:view 获取两个输入参数,即“HR”和“employees”,它们指示该函数查询属于 HR 数据库模式的员工表。因此,ora:view 将返回一个表示 HR.employees 表行的员工 XML 文档序列。但为了节省结果文档中的空间,只将前三个员工记录传递给结果序列。这是通过在 FLWOR 表达式的 where 子句中指定 $i/EMPLOYEE_ID <= 102 而实现的。请注重 FLWOR 表达式的 return 子句中使用的 xs:string() 和 xs:integer() XQuery 类型表达式。实际上,此处使用的这两个 XQuery 表达式不仅将 XML 节点值转换为相应的类型,而且还将提取这些节点值。随后,生成的员工 XML 文档作为 employees.xml 保存到之前在列表 1 中另一个 PL/SQL 过程中创建的 /public/employees XML 信息库文件夹。要确保此操作已完成,可执行以下查询:
SELECT XMLQuery('for $i in fn:doc("/public/employees/employees.xml") return; $i' RETURNING CONTENT) AS RESULT FROM DUAL;
该查询应生成以下输出:
100 King 24000
101 Kochhar 17000
102 De Haan 17000
在以上 XQuery 中,fn:doc XQuery 函数用于访问 Oracle XML DB 信息库中存储的单个 XML 文档。但假如要处理一些具有相同或相似结构的 XML 文档(存储在同一 XML 信息库文件夹中),应该怎么做?这种情况下,另一个用于处理 XML 信息库资源的 XQuery 函数(即 fn:collection)可能会派上用场。本文稍后将介绍几个有关如何使用 fn:collection XQuery 函数的示例。
查询 XMLType 数据
XQuery 使您可以操作基于 XML 模式以及非基于模式的数据。以下示例演示了如何使用 XMLTable 函数从 OE 演示数据库模式中查询基于 PurchaseOrder XML 模式的 XMLType 表。
SELECT ttab.COLUMN_VALUE AS OrderTotal FROM purchaseorder, XMLTable( 'for $i in /PurchaseOrder where $i/User = "EABEL" return; {$i/Reference} {fn:sum(for $j in $i/LineItems/LineItem/Part return ($j/@Quantity*$j/@UnitPrice))}
SELECT XMLQuery('for $i in /PurchaseOrder return; {$i/Reference} {fn:sum(for $j in $i/LineItems/LineItem/Part return ($j/@Quantity*$j/@UnitPrice))}
' PASSING OBJECT_VALUE RETURNING CONTENT) AS ordertotal FROM purchaseorder WHERE existsNode(OBJECT_VALUE, '/PurchaseOrder[User = "EABEL"]') = 1;
以上查询与本部分开头的 XMLTable 示例生成相同的输出。
查询 Oracle XML DB 信息库中的 XML 数据
为访问 Oracle XML DB 信息库中存储的 XML 数据,Oracle XQuery 引入了 fn:doc 和 fn:collection XQuery 函数。使用 fn:doc,您可以查询 XML 信息库中存储的单个 XML 文档,而 fn:collection 使您可以访问同一信息库文件夹中存储的多个 XML 文档。
正如本文之前(参阅使用关系数据构建 XML部分)介绍的示例所演示,使用 fn:doc 非常简单直接。它获取表示信息库文件资源 (URI) 的字符串并返回该 URI 指向的文档。要了解 fn:collection XQuery 函数的作用,同一文件夹中至少应有两个信息库文件。假如已经运行了列表 1 中的代码,则已经创建了 /public/employees 信息库文件夹并在其中存储了 employees.xml 文件。因此,您将需要在该文件夹中至少再创建一个 XML 文件,然后才能试用 fn:collection。列表 2 中的 PL/SQL 代码基于 SCOTT/TIGER 演示数据库模式的 dept 和 emp 表存储的关系数据构建 XML,然后将生成的 XML 文档作为 acc_dept.xml 保存到 /public/employees 信息库文件夹。要运行列表 2 中的 PL/SQL 过程,请确保以 SCOTT/TIGER 的身份登录。
列表 2:基于关系数据构建 XML 并将其保存到 XML 信息库
DECLARE XMLdoc XMLType; BEGIN SELECT XMLQuery( 'for $j in ora:view("SCOTT", "dept")/ROW where $j/DEPTNO = 10 return ( {$j/DEPTNO, $j/DNAME} { for $i in ora:view("SCOTT", "emp")/ROW where $i/DEPTNO = $j/DEPTNO return ( {$i/EMPNO, $i/ENAME, $i/SAL} )}
)' RETURNING CONTENT) INTO XMLdoc FROM DUAL; IF(DBMS_XDB.CREATERESOURCE('/public/employees/acc_dept.xml', XMLdoc)) THEN DBMS_OUTPUT.PUT_LINE('Resource is created'); ELSE DBMS_OUTPUT.PUT_LINE('Cannot create resource'); END IF; COMMIT; END; /
此时,/public/employees 信息库文件夹应包含两个文件:acc_dept.xml(由列表 2 中的 PL/SQL 代码生成)和 employees.xml 文件(由列表 1 中的代码生成)。由于这些 XML 文档存储在同一信息库文件夹中,因此可以使用 fn:collection 函数访问两个 XML 文档中存储的员工信息。然而,尽管这些 XML 文档均包含员工 XML 元素(这些元素实际上具有相同结构?XML 文档本身的结构迥然不同。在 employees.xml 中,文档根元素为 EMPLOYEES,而 acc_dept.xml 将 DEPARTMENT 用作根元素。要解决此问题,可以通过 XQuery 使用 XPath // 构造,从而导航到 XML 文档中的某个节点,而不必指定该节点的确切路径。以下示例演示了如何在 XQuery 表达式中使用 XPath // 构造:
SELECT XMLQuery( 'for $i in fn:collection("/public/employees")//EMPLOYEE where $i/SAL >= 5000 order by $i/ENAME return; $i' RETURNING CONTENT) FROM DUAL;
该构造应生成以下输出:
102 De Haan 17000
7839 KING 5000
100 King 24000
101 Kochhar 17000
您可以看到,以上输出包含从 employees.xml 和 acc_dept.xml 中获取的员工 XML 元素,这些元素表示薪酬大于或等于 5,000 美元的员工。
将 XML 分解为关系数据
假如应用程序处理关系数据而非 XML,而您需要访问的数据以 XML 格式存储,则将 XML 分解为关系数据可能会非常有用。继续进行上一部分的示例,您可以使用 SQL 函数 XMLTable 将员工 XML 元素分解为虚拟表的单个列,如下所示:
SELECT emps.empno,emps.ename, emps.sal FROM XMLTable( 'for $i in fn:collection("/public/employees")//EMPLOYEE where $i/SAL >= 5000 return; $i' COLUMNS empno NUMBER;;;;PATH '/EMPLOYEE/EMPNO', ename VARCHAR2(30) PATH '/EMPLOYEE/ENAME', salNUMBER;;;;PATH '/EMPLOYEE/SAL') emps;
该查询将生成以下输出:
EMPNO ENAME SAL ----- -------------- ---------- 7839 KING 5000 100 King;;;;;24000 101 Kochhar;;17000 102 De Haan;;17000
查询外部数据源
使用 XQuery,可以基于 XML 数据以及可以用 XML 表示的非 XML 数据生成 XML 文档,无论其位置如何:无论是存储在数据库中、置于网站上、即时创建还是存储在文件系统中。但要注重,Oracle XML DB 为针对数据库中存储的数据进行的 XML 操作提供了非常高的性能和可伸缩性。因此,假如您能够完全控制所处理的数据,则最好将它移动到数据库中。
正如您从前面的示例中了解到的,在 Oracle XQuery 实施中,doc 和 collection XQuery 函数用于访问 Oracle XML DB 信息库中存储的 XML 文档。可以通过 XMLTable 和 XMLQuery SQL 函数中的 PASSING 子句动态绑定外部数据源。考虑以下示例。假设您的公司要为那些致力于 XQ 项目的员工支付奖金。因此,财务部发布了 empsbonus.xml 文件,其中包含有资格获得奖金的员工列表以及该列表中输入的每个员工的奖金数额。empsbonus.xml 文件可能如下所示:
100 1200
101 1000
在实际情况中,以上的 XML 文件可能置于网站上(因此可以通过互联网获得)、以文件形式存储在本地文件系统中,或以文件资源形式存储在 Oracle XML DB 信息库中。就本示例而言,该文件位于网站上。为简单起见,可以在目录(Web 服务器在其中存储可从 Web 看到的文档)中创建一个员工文件夹,然后在该文件夹中插入 empsbonus.xml 文件,以便可以通过以下 URL 访问 empsbonus.xml 文件: http://localhost/employees/empsbonus.xml
接下来,假设您需要基于 empsbonus.xml 文档中存储的数据创建一个报表。在该报表中,您可能不但要包含列表中显示的奖金数额以及每个员工的员工 ID,还要包含他/她的全名。因此,可以首先使用以下查询生成一个新的 XML 文档(假设您以 HR/HR 的身份连接):
SELECT XMLQuery( 'for $k in 1 return ( {for $i in ora:view("employees")/ROW, $j in $emps/EMPLOYEES/EMPLOYEE where $i/EMPLOYEE_ID = $j/EMPNO return ( {xs:string($i/EMPLOYEE_ID)} {xs:string(fn:concat($i/FIRST_NAME, " ", $i/LAST_NAME))} {xs:integer($j/BONUS)} )} )' PASSING xmlparse (document httpuritype ('http://localhost/employees/empsbonus.xml').getCLOB()) as "emps" RETURNING CONTENT).getStringVal() as RESULT FROM DUAL;
以上查询是一个有关如何使用 XQuery 基于 XML 和非 XML 数据(以不同的方式从不同的数据源中检索)生成 XML 文档的示例。具体而言,使用 ora:view() 函数访问 HR 演示模式中的默认 employees 关系表,并使用 PASSING 子句中的 httpuritype() 函数借助于 HTTP 访问 empsbonus.xml 文档。然后,在 FLWOR 表达式的 return 子句中构建新的 XML 文档。最后,将获得以下 XML 文档:
100 Steven King 1200
101 Neena Kochhar 1000
解决性能问题
正如您从前面的部分中了解到的,XQuery 是一种用于查询 Oracle 数据库存储的 XML 内容的高效方法 - 无论您是处理本地存储的 XMLType 数据还是查询基于关系数据构建的 XML 视图。但根据对数据使用的存储类型的不同,XQuery 表达式的执行性能可能迥然不同。尤其是,Oracle XML DB 可以优化基于由 ora:view 函数创建的 SQL/XML 视图而构建的 XQuery 表达式。对于 XMLType 表或列中存储的 XML 数据,只能对使用结构化(对象-关系)存储技术存储的基于 XML 模式的 XMLType 数据进行 XQuery 优化。
所选择的存储模型并非是影响 XQuery 表达式执行性能的唯一因素。在某些情况下,XQuery 表达式本身的结构也可能导致性能问题。要监控 XQuery 表达式的性能,可以打印并检查关联的 EXPLAIN PLAN。在 SQL*Plus 中,只需设置 AUTOTRACE 系统变量,即可打印 SQL 优化程序使用的执行路径。但要执行该操作,请确保创建 PLUSTRACE 角色,然后将其授予连接到数据库所使用的用户。有关如何执行此操作的信息,请参阅 Oracle 数据库 10g 第 2 版 (10.2) 文档中《SQL*Plus 用户指南和参考》一书中的“调整 SQL*Plus”一章。以下示例演示了如何通过检查 EXPLAIN PLAN 生成的执行计划来获得好处。假设您已经将 PLUSTRACE 角色授予默认用户 OE,以 OE/OE 的身份登录并运行以下查询:
SET AUTOTRACE ON EXPLAIN SELECT count(*) FROM oe.purchaseorder, XMLTable( 'for $i in /PurchaseOrder/User where $i = "CJOHNSON" return $i' PASSING OBJECT_VALUE) ptab;
XQuery 与 XSLT
尽管 Oracle 在 Oracle XML DB 中提供了一个自带 XSLT 处理器,但在很多情况下(尤其是在处理大型文档时),XQuery 对于构建 XML 更高效。此外,XQuery 表达式通常比为同一作业设计的 XSLT 样式表更具可读性,并且更清楚。与 XSLT 一样,XQuery 不但可用于将一个 XML 文档转换为另一个 XML 文档,而且还可用于将 XML 转换为另一种基于文本的格式,如 Html 或 WML。
在本文前面的查询 XMLType 数据部分中,您看到了一个有关使用 XQuery 将一个 XML 文档转换为另一个 XML 文档的示例。具体而言,该示例使用 XQuery 表达式计算示例数据库模式 OE 的 purchaseorder 表中存储的订单的订单总计,然后为处理的每个订单生成了一个 OrderTotal XML 元素。实际上,您可以使用 XSLT 执行相同操作。为此,您首先需要创建一个τ糜?PurchaseOrder XML 文档的 XSLT 样式表,以生成相应的 OrderTotal 元素。对于此示例,可以使用列表 4 中所示的 XSLT 样式表。
列表 4:使用 XSLT 计算小计总和 (Quantity * UnitPrice)
为方便起见,您可能需要将此 XSL 样式表保存在数据库中,然后再开始使用它。例如,您可以将样式表作为文件资源保存在 Oracle XML DB 信息库中。执行该操作的方法之一是将样式表作为文件保存到本地文件系统中,然后使用以下某个互联网协议将它移动到 XML 信息库:FTP、HTTP 或 WebDAV。假设您已经将列表 4 中的 XSLT 样式表作为 orderTotal.xsl 保存在 /public 信息库文件夹中,现在可以按以下示例所示将它用作 XMLTransform SQL 函数的参数(假设您以 OE/OE 的身份登录):
SELECT XMLTRANSFORM(OBJECT_VALUE, xdbUriType('/public/orderTotal.xsl').getXML()).GetStringVal() AS RESULT FROM purchaseorder WHERE existsNode(OBJECT_VALUE, '/PurchaseOrder[User = "EABEL"]') = 1;
' PASSING xmlparse (document httpuritype ('http://www.oracle.com/technology/syndication/rss_otn_news.xml').getCLOB()) as "h" RETURNING CONTENT).getStringVal() as RESULT FROM DUAL;
该 XQuery 应生成一个 XML 文档,其中包含 Oracle 技术网 (OTN) 最近发布的与 PHP 技术相关的头条新闻列表。所生成的 XML 文档可能如下所示: