[转载]Oracle XQuery查询、构建和转换XML
<P>信息来源:邪恶八进制信息安全团队</P><P>在 Oracle 数据库 10<EM>g</EM> 第 2 版中,Oracle 引入了一个与该数据库集成的全功能自带 XQuery 引擎,该引擎可用于完成与开发支持 XML 的应用程序相关的各种任务。XQuery 是一种用于处理 XML 数据模型的查询语言,它实际上可操作任何类型的可用 XML 表达的数据。尽管 Oracle XQuery 实施使您可以使用数据库数据和外部数据源,但在处理数据库中存储的结构化数据方面,Oracle XML DB 通常可以显著提高性能。 </P>
<P> 本文提供的示例不仅演示了在什么场合下以及如何使用 XQuery 查询、构建和转换 XML,而且还演示了如何监控和分析 XQuery 表达式的性能执行,从而找到更高效的方法来处理同一工作负载。 </P>
<P><SPAN class=parahead1> 基于关系数据构建 XML</SPAN></P>
<P> 在需要的情况下(例如,向 Web 服务发送结果),您可能要基于关系数据构建 XML。要在 Oracle 数据库 10<EM>g</EM> 第 2 版之前的版本中完成此任务,通常需要使用 SQL/XML 生成函数,如 XMLElement、XMLForest 和 XMLAgg()。在 Oracle 数据库 10<EM> g</EM> 第 2 版中,XQuery 将比这些函数更为高效。具体而言,在 XQuery 表达式内部使用 ora:view XQuery 函数,您可以查询现有的关系表或视图以及即时构建 XML,从而不必通过关系数据显式创建 XML 视图。列表 1 中的 PL/SQL 代码演示了如何使用 ora:view 基于示例数据库模式 HR 的默认员工关系表中存储的数据构建 XML 文档。 </P>
<P><STRONG> 列表 1:使用 ora:view 基于关系数据创建 XML</STRONG></P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>BEGIN<BR>IF(DBMS_XDB.CREATEFOLDER('/public/employees')) THEN<BR>DBMS_OUTPUT.PUT_LINE('Folder is created');<BR>ELSE<BR>DBMS_OUTPUT.PUT_LINE('Cannot create folder');<BR>END IF;<BR>COMMIT;<BR>END;<BR>/</PRE><PRE>DECLARE<BR>XMLdoc XMLType;<BR>BEGIN<BR>SELECT XMLQuery(<BR>'for $j in 1<BR>return (<BR> {<BR>for $i in ora:view("HR", "employees")/ROW<BR>where $i/EMPLOYEE_ID <= 102<BR>return (<BR>{xs:string($i/EMPLOYEE_ID)}<BR>{xs:string($i/LAST_NAME)}<BR>{xs:integer($i/SALARY)}<BR> )} )'<BR>RETURNING CONTENT) INTO XMLdoc FROM DUAL;<BR>IF(DBMS_XDB.CREATERESOURCE('/public/employees/employees.xml', XMLdoc)) THEN<BR>DBMS_OUTPUT.PUT_LINE('Resource is created');<BR>ELSE<BR>DBMS_OUTPUT.PUT_LINE('Cannot create resource');<BR>END IF;<BR>COMMIT;<BR>END;<BR>/</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P style="TEXT-INDENT: 2em">在列表 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 表达式的 <TT><FONT face=新宋体>where</FONT></TT> 子句中指定 <TT><FONT face=新宋体>$i/EMPLOYEE_ID <= 102</FONT></TT> 而实现的。请注意 FLWOR 表达式的 <TT><FONT face=新宋体>return</FONT></TT> 子句中使用的 <TT><FONT face=新宋体>xs:string()</FONT></TT> 和 <TT><FONT face=新宋体>xs:integer()</FONT></TT> XQuery 类型表达式。实际上,此处使用的这两个 XQuery 表达式不仅将 XML 节点值转换为相应的类型,而且还将提取这些节点值。随后,生成的员工 XML 文档作为 employees.xml 保存到之前在列表 1 中另一个 PL/SQL 过程中创建的 /public/employees XML 信息库文件夹。要确保此操作已完成,可执行以下查询:</P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT XMLQuery('for $i in fn:doc("/public/employees/employees.xml")<BR>return;<BR>$i'<BR>RETURNING CONTENT) AS RESULT FROM DUAL;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 该查询应生成以下输出:</P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><EMPLOYEES><BR><EMPLOYEE><BR><EMPNO>100</EMPNO><BR><ENAME>King</ENAME><BR><SAL>24000</SAL><BR></EMPLOYEE><BR><EMPLOYEE><BR><EMPNO>101</EMPNO><BR><ENAME>Kochhar</ENAME><BR><SAL>17000</SAL><BR></EMPLOYEE><BR><EMPLOYEE><BR><EMPNO>102</EMPNO><BR><ENAME>De Haan</ENAME><BR><SAL>17000</SAL><BR></EMPLOYEE><BR></EMPLOYEES></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 在以上 XQuery 中,fn:doc XQuery 函数用于访问 Oracle XML DB 信息库中存储的单个 XML 文档。但如果要处理一些具有相同或相似结构的 XML 文档(存储在同一 XML 信息库文件夹中),应该怎么做?这种情况下,另一个用于处理 XML 信息库资源的 XQuery 函数(即 fn:collection)可能会派上用场。本文稍后将介绍几个有关如何使用 fn:collection XQuery 函数的示例。 </P>
<P><SPAN class=parahead1> 查询 XMLType 数据</SPAN></P>
<P> XQuery 使您可以操作基于 XML 模式以及非基于模式的数据。以下示例演示了如何使用 XMLTable 函数从 OE 演示数据库模式中查询基于 PurchaseOrder XML 模式的 XMLType 表。 </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT ttab.COLUMN_VALUE AS OrderTotal FROM purchaseorder,<BR>XMLTable(<BR>'for $i in /PurchaseOrder<BR>where $i/User = "EABEL"<BR>return;<BR><ORDERTOTAL><BR>{$i/Reference}<BR><TOTAL><BR>{fn:sum(for $j in $i/LineItems/LineItem/Part<BR>return ($j/@Quantity*$j/@UnitPrice))}<BR></TOTAL><BR></ORDERTOTAL>'<BR>PASSING OBJECT_VALUE<BR>) ttab;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P style="TEXT-INDENT: 2em">在以上示例中,您在 XMLTable 函数的 PASSING 子句中使用 OBJECT_VALUE 虚拟列将 purchaseorder 表作为上下文项传递给此处使用的 XQuery 表达式。XQuery 表达式计算用户 EABEL 请求的每个购买订单的总计,并为处理的每个订单生成一个 OrderTotal XML 元素。要访问生成的 XML,请使用 SELECT 列表中的 COLUMN_VALUE 虚拟列。最终的输出应如下所示:</P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>ORDERTOTAL<BR>-------------------------------------------------------------<BR><ORDERTOTAL><BR><REFERENCE>EABEL-20021009123338324PDT</REFERENCE><BR><TOTAL>1328.05</TOTAL><BR></ORDERTOTAL><BR><ORDERTOTAL><BR><REFERENCE>EABEL-20021009123335791PDT</REFERENCE><BR><TOTAL>2067.15</TOTAL><BR></ORDERTOTAL><BR><ORDERTOTAL><BR><REFERENCE>EABEL-20021009123336251PDT</REFERENCE><BR><TOTAL>289.6</TOTAL><BR></ORDERTOTAL><BR><ORDERTOTAL><BR><REFERENCE>EABEL-20021009123336382PDT</REFERENCE><BR><TOTAL>928.92</TOTAL><BR></ORDERTOTAL></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 要获得相同的最终结果,可以改用 XMLQuery 函数。但如果将上一个示例中使用的 XQuery 表达式参数传递给 XMLQuery(如下所示):</P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT XMLQuery('for $i in /PurchaseOrder<BR>where $i/User eq "EABEL"<BR>return <ORDERTOTAL><BR>{$i/Reference}<BR><TOTAL><BR>{fn:sum(for $j in $i/LineItems/LineItem/Part<BR>return ($j/@Quantity*$j/@UnitPrice))}<BR></TOTAL><BR></ORDERTOTAL>'<BR>PASSING OBJECT_VALUE<BR>RETURNING CONTENT)<BR>FROM purchaseorder;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 则 XQuery 表达式返回的空序列将与 purchaseorder 表联接,从而包含在查询总结果集中。实际上,这意味着输出将不仅包含为用户 EABEL 请求的订单生成的 OrderTotal 元素,而且还包含为 purchaseorder 表中存储的所有其他订单生成的空行(默认情况下,purchaseorder 表包含 132 行)。从结果集中排除空行的方法之一是在 SELECT 语句的 WHERE 子句中使用 existsNode SQL 函数,而不是在 XQuery 表达式中使用 WHERE 子句,如下所示: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT XMLQuery('for $i in /PurchaseOrder<BR>return <ORDERTOTAL><BR>{$i/Reference}<BR><TOTAL><BR>{fn:sum(for $j in $i/LineItems/LineItem/Part<BR>return ($j/@Quantity*$j/@UnitPrice))}<BR></TOTAL><BR></ORDERTOTAL>'<BR>PASSING OBJECT_VALUE<BR>RETURNING CONTENT) AS ordertotal<BR>FROM purchaseorder<BR>WHERE existsNode(OBJECT_VALUE, '/PurchaseOrder[User = "EABEL"]') = 1;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 以上查询与本部分开头的 XMLTable 示例生成相同的输出。 </P>
<P><SPAN class=parahead1> 查询 Oracle XML DB 信息库中的 XML 数据</SPAN></P><SPAN class=parahead1>
<P style="TEXT-INDENT: 2em">为访问 Oracle XML DB 信息库中存储的 XML 数据,Oracle XQuery 引入了 fn:doc 和 fn:collection XQuery 函数。使用 fn:doc,您可以查询 XML 信息库中存储的单个 XML 文档,而 fn:collection 使您可以访问同一信息库文件夹中存储的多个 XML 文档。 </P>
<P> 正如本文之前(参阅使用关系数据构建 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 的身份登录。 </P>
<P><STRONG> 列表 2:基于关系数据构建 XML 并将其保存到 XML 信息库</STRONG> </P><PRE><CCID_NOBR><CENTER><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>DECLARE<BR>XMLdoc XMLType;<BR>BEGIN<BR>SELECT XMLQuery(<BR>'for $j in ora:view("SCOTT", "dept")/ROW<BR>where $j/DEPTNO = 10<BR>return (<DEPARTMENT> <BR>{$j/DEPTNO,<BR>$j/DNAME}<BR><EMPLOYEES> {<BR>for $i in ora:view("SCOTT", "emp")/ROW<BR>where $i/DEPTNO = $j/DEPTNO<BR>return (<BR><EMPLOYEE><BR>{$i/EMPNO,<BR>$i/ENAME,<BR>$i/SAL}<BR></EMPLOYEE>)} <BR></EMPLOYEES><BR></DEPARTMENT>)'<BR>RETURNING CONTENT) INTO XMLdoc FROM DUAL;<BR>IF(DBMS_XDB.CREATERESOURCE('/public/employees/acc_dept.xml', XMLdoc)) THEN<BR>DBMS_OUTPUT.PUT_LINE('Resource is created');<BR>ELSE<BR>DBMS_OUTPUT.PUT_LINE('Cannot create resource');<BR>END IF;<BR>COMMIT;<BR>END;<BR>/</PRE></TD></TR></TBODY></TABLE></CENTER><CENTER><P style="TEXT-INDENT: 2em">此时,/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 // 构造: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT XMLQuery(<BR>'for $i in fn:collection("/public/employees")//EMPLOYEE<BR>where $i/SAL >= 5000<BR>order by $i/ENAME<BR>return;<BR>$i'<BR>RETURNING CONTENT) FROM DUAL;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE> 该构造应生成以下输出: <PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><EMPLOYEE><BR><EMPNO>102</EMPNO><BR><ENAME>De Haan</ENAME><BR><SAL>17000</SAL><BR></EMPLOYEE><BR><EMPLOYEE><BR><EMPNO>7839</EMPNO><BR><ENAME>KING</ENAME><BR><SAL>5000</SAL><BR></EMPLOYEE><BR><EMPLOYEE><BR><EMPNO>100</EMPNO><BR><ENAME>King</ENAME><BR><SAL>24000</SAL><BR></EMPLOYEE><BR><EMPLOYEE><BR><EMPNO>101</EMPNO><BR><ENAME>Kochhar</ENAME><BR><SAL>17000</SAL><BR></EMPLOYEE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE><P> 您可以看到,以上输出包含从 employees.xml 和 acc_dept.xml 中获取的员工 XML 元素,这些元素表示薪酬大于或等于 5,000 美元的员工。 </P><P><SPAN class=parahead1> 将 XML 分解为关系数据</SPAN></P><P> 如果应用程序处理关系数据而非 XML,而您需要访问的数据以 XML 格式存储,则将 XML 分解为关系数据可能会非常有用。继续进行上一部分的示例,您可以使用 SQL 函数 XMLTable 将员工 XML 元素分解为虚拟表的单个列,如下所示: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT emps.empno,emps.ename, emps.sal FROM <BR>XMLTable(<BR>'for $i in fn:collection("/public/employees")//EMPLOYEE<BR>where $i/SAL >= 5000<BR>return;<BR>$i'<BR>COLUMNS empno NUMBER PATH '/EMPLOYEE/EMPNO',<BR>ename VARCHAR2(30) PATH '/EMPLOYEE/ENAME',<BR>sal NUMBER PATH '/EMPLOYEE/SAL') emps;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE></CENTER></CCID_NOBR></PRE></SPAN>
<P style="TEXT-INDENT: 2em">该查询将生成以下输出: <PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>EMPNO ENAME SAL<BR>----- -------------- ----------<BR>7839 KING 5000<BR>100 King 24000<BR>101 Kochhar 17000<BR>102 De Haan 17000</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P><SPAN class=parahead1> 查询外部数据源</SPAN></P>
<P> 使用 XQuery,可以基于 XML 数据以及可以用 XML 表示的非 XML 数据生成 XML 文档,无论其位置如何:无论是存储在数据库中、置于网站上、即时创建还是存储在文件系统中。但要注意,Oracle XML DB 为针对数据库中存储的数据进行的 XML 操作提供了非常高的性能和可伸缩性。因此,如果您能够完全控制所处理的数据,则最好将它移动到数据库中。 </P>
<P> 正如您从前面的示例中了解到的,在 Oracle XQuery 实施中,doc 和 collection XQuery 函数用于访问 Oracle XML DB 信息库中存储的 XML 文档。可以通过 XMLTable 和 XMLQuery SQL 函数中的 PASSING 子句动态绑定外部数据源。考虑以下示例。假设您的公司要为那些致力于 XQ 项目的员工支付奖金。因此,财务部发布了 empsbonus.xml 文件,其中包含有资格获得奖金的员工列表以及该列表中输入的每个员工的奖金数额。empsbonus.xml 文件可能如下所示: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><EMPLOYEES><BR><EMPLOYEE><BR><EMPNO>100</EMPNO><BR><BONUS>1200</BONUS><BR></EMPLOYEE><BR><EMPLOYEE><BR><EMPNO>101</EMPNO><BR><BONUS>1000</BONUS><BR></EMPLOYEE><BR></EMPLOYEES></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 在实际情况中,以上的 XML 文件可能置于网站上(因此可以通过互联网获得)、以文件形式存储在本地文件系统中,或以文件资源形式存储在 Oracle XML DB 信息库中。就本示例而言,该文件位于网站上。为简单起见,可以在目录(Web 服务器在其中存储可从 Web 看到的文档)中创建一个员工文件夹,然后在该文件夹中插入 empsbonus.xml 文件,以便可以通过以下 URL 访问 empsbonus.xml 文件: </P><PRE> [url]http://localhost/employees/empsbonus.xml[/url]</PRE>
<P> 接下来,假设您需要基于 empsbonus.xml 文档中存储的数据创建一个报表。在该报表中,您可能不但要包含列表中显示的奖金数额以及每个员工的员工 ID,还要包含他/她的全名。因此,可以首先使用以下查询生成一个新的 XML 文档(假设您以 HR/HR 的身份连接): </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT XMLQuery(<BR>'for $k in 1<BR>return (<BR><EMPLOYEES> {for $i in ora:view("employees")/ROW,<BR>$j in $emps/EMPLOYEES/EMPLOYEE<BR>where $i/EMPLOYEE_ID = $j/EMPNO<BR>return (<EMPLOYEE><BR><EMPNO>{xs:string($i/EMPLOYEE_ID)}</EMPNO><BR><NAME>{xs:string(fn:concat($i/FIRST_NAME, " ", $i/LAST_NAME))}</NAME><BR><BONUS>{xs:integer($j/BONUS)}</BONUS><BR></EMPLOYEE>)} </EMPLOYEES>)'<BR>PASSING xmlparse (document httpuritype<BR>('http://localhost/employees/empsbonus.xml').getCLOB()) as "emps"<BR>RETURNING CONTENT).getStringVal() as RESULT FROM DUAL;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P style="TEXT-INDENT: 2em">以上查询是一个有关如何使用 XQuery 基于 XML 和非 XML 数据(以不同的方式从不同的数据源中检索)生成 XML 文档的示例。具体而言,使用 <TT><FONT face=新宋体>ora:view()</FONT></TT> 函数访问 <TT><FONT face=新宋体>HR</FONT></TT> 演示模式中的默认 <TT><FONT face=新宋体>employees</FONT></TT> 关系表,并使用 <TT><FONT face=新宋体>PASSING</FONT></TT> 子句中的 <TT><FONT face=新宋体>httpuritype()</FONT></TT> 函数借助于 HTTP 访问 empsbonus.xml 文档。然后,在 <TT><FONT face=新宋体>FLWOR</FONT></TT> 表达式的 <TT><FONT face=新宋体>return</FONT></TT> 子句中构建新的 XML 文档。最后,将获得以下 XML 文档: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><EMPLOYEES><BR><EMPLOYEE><BR><EMPNO>100</EMPNO><BR><NAME>Steven King</NAME><BR><BONUS>1200</BONUS><BR></EMPLOYEE><BR><EMPLOYEE><BR><EMPNO>101</EMPNO><BR><NAME>Neena Kochhar</NAME><BR><BONUS>1000</BONUS><BR></EMPLOYEE><BR></EMPLOYEES></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P><SPAN class=parahead1> 解决性能问题</SPAN></P>
<P> 正如您从前面的部分中了解到的,XQuery 是一种用于查询 Oracle 数据库存储的 XML 内容的高效方法 - 无论您是处理本地存储的 XMLType 数据还是查询基于关系数据构建的 XML 视图。但根据对数据使用的存储类型的不同,XQuery 表达式的执行性能可能迥然不同。尤其是,Oracle XML DB 可以优化基于由 ora:view 函数创建的 SQL/XML 视图而构建的 XQuery 表达式。对于 XMLType 表或列中存储的 XML 数据,只能对使用结构化(对象-关系)存储技术存储的基于 XML 模式的 XMLType 数据进行 XQuery 优化。 </P>
<P> 所选择的存储模型并非是影响 XQuery 表达式执行性能的唯一因素。在某些情况下,XQuery 表达式本身的结构也可能导致性能问题。要监控 XQuery 表达式的性能,可以打印并检查关联的 EXPLAIN PLAN。在 SQL*Plus 中,只需设置 AUTOTRACE 系统变量,即可打印 SQL 优化程序使用的执行路径。但要执行该操作,请确保创建 PLUSTRACE 角色,然后将其授予连接到数据库所使用的用户。有关如何执行此操作的信息,请参阅 Oracle 数据库 10g 第 2 版 (10.2) 文档中<EM>《SQL*Plus 用户指南和参考》</EM>一书中的“调整 SQL*Plus”一章。以下示例演示了如何通过检查 EXPLAIN PLAN 生成的执行计划来获得好处。假设您已经将 PLUSTRACE 角色授予默认用户 OE,以 OE/OE 的身份登录并运行以下查询: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SET AUTOTRACE ON EXPLAIN</PRE><PRE><BR>SELECT count(*)<BR>FROM oe.purchaseorder, XMLTable(<BR>'for $i in /PurchaseOrder/User<BR>where $i = "CJOHNSON"<BR>return $i'<BR>PASSING OBJECT_VALUE) ptab;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 这将生成以下输出: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>COUNT(*)<BR>----------<BR> 9</PRE><PRE>Execution Plan<BR>----------------------------------------------------<BR>Plan hash value: 4046110317</PRE><PRE>----------------------------------------------------------------------------------------<BR>| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |<BR>----------------------------------------------------------------------------------------<BR>| 0 | SELECT STATEMENT | | 1 | 226 | 29 (0) | 00:00:01 |</PRE><PRE>| 1 | SORT AGGREGATE | | 1 | 226 | | |</PRE><PRE>| 2 | NESTED LOOPS | | 10782 | 2379K | 29 (0) | 00:00:01 |</PRE><PRE>|* 3 | TABLE ACCESS FULL | PURCHASEORDER | 1 | 226 | 5 (0) | 00:00:01 |</PRE><PRE>| 4 | COLLECTION ITERATOR P| XMLSEQUENCEFROMX| | | | |</PRE><PRE><BR>Predicate Information (identified by operation id):<BR>---------------------------------------------------</PRE><PRE>3 - filter(SYS_CHECKACL("ACLOID","OWNERID",xmltype('<PRIVILEGE<BR>...</PRE></TD></TR></TBODY></TABLE></CENTER></PRE>
<P style="TEXT-INDENT: 2em">您可能对为以上查询生成的执行计划并不满意。尤其是,所处理的行数可能非常大。由于 SQL 调整的主要目标是避免访问对结果没有任何影响的行,因此可能要继续调整查询以优化性能。对查询中包含的 XPath 表达式进行重新建模后,可以再次重试它,如下所示: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT count(*)<BR>FROM oe.purchaseorder, XMLTable(<BR>'for $i in /PurchaseOrder<BR>where $i/User = "CJOHNSON"<BR>return $i/User'<BR>PASSING OBJECT_VALUE) ptab;</PRE><PRE>这次,输出应如下所示: <BR>COUNT(*)<BR>----------<BR> 9</PRE><PRE><BR>Execution Plan<BR>---------------------------------------------------<BR>Plan hash value: 3411896580</PRE><PRE>----------------------------------------------------------------------------------------<BR>| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |<BR>----------------------------------------------------------------------------------------<BR>| 0 | SELECT STATEMENT | | 1 | 29 | 7 (0) | 00:00:01 |</PRE><PRE>| 1 | SORT AGGREGATE | | 1 | 29 | | |</PRE><PRE>| 2 | NESTED LOOPS | | 1 | 29 | 7 (0) | 00:00:01 |</PRE><PRE>| 3 | FAST DUAL | | 1 | | 2 (0) | 00:00:01 |</PRE><PRE>|* 4 | TABLE ACCESS FULL | PURCHASEORDER | 1 | 29 | 5 (0) | 00:00:01 |</PRE><PRE><BR>Predicate Information (identified by operation id):<BR>---------------------------------------------------</PRE><PRE>4 - filter("PURCHASEORDER"."SYS_NC00022$"='CJOHNSON' AND<BR>SYS_CHECKACL("ACLOID","OWNERID",xmltype('<PRIVILEGE<BR>...</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE><PRE></PRE>
<P> 您可以看到,以上显示的查询生成相同的最终结果,但它们的执行计划并不相同。查看最后一个示例中的 XQuery 表达式,您可能会注意到它迭代顶层 PurchaseOrder 元素,其中的每个 PurchaseOrder 元素都表示基于 PurchaseOrder XMLType 模式的表中的一行。这意味着实际上重写 XQuery 表达式,以迭带基础对象表(用于存储分解的 PurchaseOrder 文档)中的行。与查询要迭代不表示基础表中的单个行的 XML 元素相比,该方法的性能更好一些。 </P>
<P> 但在某些情况下,很难发现 XQuery 表达式的哪个构造将使某些查询的性能更好。这就是为什么最好在开发阶段使用调整工具的原因。 </P>
<P><SPAN class=parahead1> 将动态变量绑定到 XQuery 表达式</SPAN></P><SPAN class=parahead1>
<P style="TEXT-INDENT: 2em">另一种可以显著提高 XQuery 表达式执行性能的技术是使用绑定动态变量。使用绑定变量(而不是将变量串联为字符串)可以使 Oracle 重用 SQL 语句,从而减少分析开销并显著提高应用程序的性能。可以在 XMLQuery 和 XMLTable SQL 函数中使用 PASSING 子句将动态变量绑定到 XQuery 表达式。该技术使您可以根据客户端代码中计算的参数动态生成 XML。列表 3 中的示例演示了如何在从 PHP 脚本执行的 XQuery 查询中使用绑定变量。 </P>
<P><STRONG> 列表 3:使用绑定变量</STRONG> </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><?php <BR>//File:BindVars.php<BR>$user = 'hr';<BR>$pswd = 'hr';<BR>$db ='(DESCRIPTION=<BR>(ADDRESS_LIST=<BR>(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))<BR> )<BR>(CONNECT_DATA=(SID=orclR2)(SERVER=DEDICATED))<BR> )';<BR>$empno=100;<BR>$conn = oci_connect($user, $pswd, $db);<BR>$sql = 'SELECT XMLQuery('."'".'for $i in ora:view("employees")/ROW<BR>where $i/EMPLOYEE_ID = $empno<BR>return (<EMPLOYEE><BR>{$i/EMPLOYEE_ID,<BR>$i/EMAIL,<BR>$i/JOB_ID}<BR></EMPLOYEE>)'."'".'PASSING XMLElement("empno", :empno) AS "empno"<BR>RETURNING CONTENT).GetStringVal() AS RESULT FROM DUAL';<BR>$query = oci_parse($conn, $sql);<BR>oci_bind_by_name($query, ":empno", $empno, 3);<BR>oci_execute($query);<BR>oci_fetch($query);<BR>$str = oci_result($query, 'RESULT');<BR>print $str;<BR>?></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 列表 3 中显示的脚本应生成以下输出(注意,浏览器中可能不会显示标记): </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><EMPLOYEE><BR><EMPLOYEE_ID>100</EMPLOYEE_ID><BR><EMAIL>SKING</EMAIL><BR><JOB_ID>AD_PRES</JOB_ID><BR></EMPLOYEE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P><SPAN class=parahead1> XQuery 与 XSLT</SPAN></P>
<P> 尽管 Oracle 在 Oracle XML DB 中提供了一个自带 XSLT 处理器,但在很多情况下(尤其是在处理大型文档时),XQuery 对于构建 XML 更高效。此外,XQuery 表达式通常比为同一作业设计的 XSLT 样式表更具可读性,并且更清楚。与 XSLT 一样,XQuery 不但可用于将一个 XML 文档转换为另一个 XML 文档,而且还可用于将 XML 转换为另一种基于文本的格式,如 HTML 或 WML。 </P>
<P style="TEXT-INDENT: 2em">在本文前面的查询 XMLType 数据部分中,您看到了一个有关使用 XQuery 将一个 XML 文档转换为另一个 XML 文档的示例。具体而言,该示例使用 XQuery 表达式计算示例数据库模式 OE 的 purchaseorder 表中存储的订单的订单总计,然后为处理的每个订单生成了一个 OrderTotal XML 元素。实际上,您可以使用 XSLT 执行相同操作。为此,您首先需要创建一个应用于 PurchaseOrder XML 文档的 XSLT 样式表,以生成相应的 OrderTotal 元素。对于此示例,可以使用列表 4 中所示的 XSLT 样式表。 </P>
<P><STRONG> 列表 4:使用 XSLT 计算小计总和 (Quantity * UnitPrice)</STRONG> </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><?xml version='1.0' encoding='utf-8' ?><BR><?XML:NAMESPACE PREFIX = XSL /><XSL:STYLESHEET xmlns:xsl="<A href=" http: www.w3.org 1999 XSL Transform?>[url]http://www.w3.org/1999/XSL/Transform[/url]</A>" version="1.0"><BR><XSL:OUTPUT method="xml" <BR><XSL:TEMPLATE match="/"><BR><XSL:FOR-EACH select="PurchaseOrder"><BR><XSL:VARIABLE <BR select="Reference" name="reference"><XSL:VARIABLE <BR select="LineItems/LineItem/Part" name="Parts"><XSL:VARIABLE name="orderTotal"><BR><XSL:CALL-TEMPLATE name="orderAmount"><BR><XSL:WITH-PARAM <BR select="$Parts" name="Parts"><XSL:WITH-PARAM <BR select="0" name="sum" ></XSL:CALL-TEMPLATE><BR></XSL:VARIABLE><BR><ORDERTOTAL> <BR><REFERENCE><XSL:VALUE-OF select="$reference"></XSL:VALUE-OF></REFERENCE> <BR><TOTAL><XSL:VALUE-OF select="format-number($orderTotal, '######0.00')"></XSL:VALUE-OF></TOTAL><BR></ORDERTOTAL><BR></XSL:FOR-EACH><BR></XSL:TEMPLATE><BR><XSL:TEMPLATE name="orderAmount"><BR><XSL:PARAM name="Parts"></XSL:PARAM><BR><XSL:PARAM name="sum"></XSL:PARAM><BR><XSL:CHOOSE><BR><XSL:WHEN test="not($Parts)"><BR><XSL:COPY-OF select="$sum"></XSL:COPY-OF><BR></XSL:WHEN><BR><XSL:OTHERWISE><BR><XSL:VARIABLE select="$sum + $Parts[1]/@Quantity * $Parts[1]/@UnitPrice" name="collector"></XSL:VARIABLE><BR><XSL:CALL-TEMPLATE name="orderAmount"><BR><XSL:WITH-PARAM select="$Parts[position()>=2]" name="Parts"></XSL:WITH-PARAM><BR><XSL:WITH-PARAM select="$collector" name="sum"></XSL:WITH-PARAM><BR></XSL:CALL-TEMPLATE><BR></XSL:OTHERWISE><BR></XSL:CHOOSE><BR></XSL:TEMPLATE><BR></XSL:STYLESHEET></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 为方便起见,您可能需要将此 XSL 样式表保存在数据库中,然后再开始使用它。例如,您可以将样式表作为文件资源保存在 Oracle XML DB 信息库中。执行该操作的方法之一是将样式表作为文件保存到本地文件系统中,然后使用以下某个互联网协议将它移动到 XML 信息库:FTP、HTTP 或 WebDAV。假设您已经将列表 4 中的 XSLT 样式表作为 orderTotal.xsl 保存在 /public 信息库文件夹中,现在可以按以下示例所示将它用作 XMLTransform SQL 函数的参数(假设您以 OE/OE 的身份登录): </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT XMLTRANSFORM(OBJECT_VALUE,<BR>xdbUriType('/public/orderTotal.xsl').getXML()).GetStringVal() AS RESULT FROM<BR>purchaseorder WHERE existsNode(OBJECT_VALUE, '/PurchaseOrder[User = "EABEL"]') = 1;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 以上查询将处理用户 EABEL 请求的所有订单(即存储在 XMLType 的默认 PurchaseOrder 表中的订单)并将生成与查询 XMLType 数据部分中的 XQuery 查询相同的输出。 </P>
<P> 将列表 4 中的 orderTotal XSLT 样式表与查询 XMLType 数据部分中的示例使用的 XQuery 表达式进行比较,您可能会注意到,XQuery 方法要比 XSLT 方法更具吸引力。至少在使用 XQuery 时,您只需编写很少的代码即可获得相同的最终结果。 </P>
<P><SPAN class=parahead1> 查询 RSS 新闻提供</SPAN></P>
<P> 由于 RSS 新闻提供本质上是一个托管的 XML 文件(RSS 新闻阅读器从中获取头条新闻或其他内容),因此可以像处理任何其他可以通过 Web 获得的 XML 文档那样来处理它。正如您在本文前面的查询外部数据源部分中所见,可以使用 XQuery 查询任何可以通过 URL 访问的 XML。您通过 XMLTable 和 XMLQuery SQL 函数中的 PASSING 子句动态绑定所有外部 XML 数据源。以下是一个查询 RSS 新闻提供的 XQuery 示例: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE>SELECT XMLQuery(<BR>'for $i in $h//channel<BR>return;<BR><HEADLINES><BR><BR>{$i/lastBuildDate}<BR><ITEM><BR>{for $j in $h//item<BR>where ora:contains($j, "PHP")<BR>return <ITEM> {($j/title, $j/link)}</ITEM>}<BR></ITEM><BR></HEADLINES>'<BR>PASSING xmlparse (document httpuritype<BR>('http://www.oracle.com/technology/syndication/rss_otn_news.xml').getCLOB()) as "h"<BR>RETURNING CONTENT).getStringVal() as RESULT FROM DUAL;</PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 该 XQuery 应生成一个 XML 文档,其中包含 Oracle 技术网 (OTN) 最近发布的与 PHP 技术相关的头条新闻列表。所生成的 XML 文档可能如下所示: </P><PRE><CENTER><CCID_NOBR><TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1><TBODY><TR><TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><HEADLINES><BR><BR><LASTBUILDDATE>Tue, 01 Nov 2005 19:37:42 GMT</LASTBUILDDATE><BR><ITEMS><BR><ITEM><BR><BR><LINK>[url]http://www.oracle.com/technology/xe[/url]</LINK><BR></ITEM><BR><ITEM><BR><BR><LINK>[url]http://www.oracle.com/technology/pub/articles/oracle_php_cookbook[/url]</LINK><BR></ITEM><BR><ITEM><BR><BR><LINK>[url]http://www.oracle.com/technology/tech/php/zendcore/index.html[/url]</LINK><BR></ITEM><BR></ITEMS><BR></HEADLINES></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER></PRE>
<P> 但在开发实际应用程序时,您将很可能需要 XQuery 表达式直接生成 HTML 标记,而不是仅仅生成一个如上所示的 XML 文档。这样,您便可以构建一个更灵活、可维护性更高的应用程序,原因是在这种情况下,所有 RSS 处理(从提取必要的数据到将它包装在 HTML 标记中)都将转移到数据库。这使您不必编写负责 RSS 处理的应用程序代码。实际上这意味着您不必在诸如 RSS 新闻提供的结构已经更改的情况下修改应用程序代码。相反,您只需修改用于 RSS 处理的 XQuery 表达式。 </P>
<P><SPAN class=parahead1> 总结</SPAN></P>
<P> 您已经在本文了解到,XQuery 是一个综合的查询语言,它提供了一种用于查询、构建和转换 XML 数据的高效方法。尽管 Oracle XQuery 实施使您可以操作任何可以用 XML 表示的数据(无论它存储在数据库中、位于网站上还是存储在文件系统中),但将处理的数据移动到数据库中始终是一个不错的主意。对于数据库中存储的数据,Oracle XML DB(对 XPath 重写使用同一机制)只能显著优化处理那些基于以下数据构建的 XQuery 表达式:这些数据包括关系数据、对象-关系数据或使用结构化(对象-关系)存储技术存储的基于 XML 模式的 XMLType 数据。<BR></P></XSL:WITH-PARAM></XSL:WITH-PARAM></XSL:VARIABLE></XSL:VARIABLE></XSL:OUTPUT></SPAN>
页:
[1]