级别: 中级 Rex Oliva (roliva@us.ibm.com), IBM DB2 顾问, IBM, Intel, Microsoft,HP
2007 年 3 月 26 日
SELECT、INSERT 、UPDATE 和 DELETE 语句都可以使用新的 IBM® DB2® 9 SQL/XML 函数,这些函数可以在几种不同的环境中运行。DB2 命令行处理程序、第三方查询工具和 SQL 存储过程都是符合要求的环境。存储过程可以结合使用过程逻辑结构,比如变量、IF/THEN/ELSE 逻辑、循环、游标和错误捕获。本文研究如何使用过程逻辑操作 XML 数据。提供的简短代码示例和解释将帮助您尽快熟悉 SQL 存储过程中的 XML 操作。
简介
与以前的 DB2 版本相比,IBM® DB2® 9 for Linux, UNIX®, and Windows® 的主要改进是添加了新的 XML 功能。在 DB2 9 之前,可以将 XML 文档存储为 LOB,或者将传入的 XML 数据分解到关系表中,这个过程需要使用 XML Extender。但是,DB2 9 使用户能够按照 XML 固有的层次化格式存储和查询 XML 数据 —— 这种技术称为 pureXML™ 支持。现在,可以在 XML 数据上建立索引,并通过调用存储过程执行分解。
本文讨论如何在 SQL 存储过程中使用 XML。我提供了许多代码示例来帮助演示特定的技术要点。提供示例的目的在于帮助您理解,因此示例要尽可能简单。为此,示例可能有点儿不够真实。
注意:本文假设您是应用程序开发人员或 DBA,而且已经熟悉如何编写 SQL 存储过程。
XML 简介
XML 表示 “可扩展标记语言(extensible markup language)”,这是一种表示信息的层次化方法,它的语法与 HTML 非常相似。XML 文档是一个由 “元素(element)” 或分支组成的树结构。每个元素有名称,可以包含文本值,可以具有一系列属性(格式为 “name=value”),可以有子元素。允许重复的元素名称,但是一个元素中的属性名称必须是惟一的。
XQuery 是一种用于 XML 的查询语言,它基于 W3C 标准。XQuery 常用于在关系数据库外查询 XML 数据,这些数据可能存储在文本文件中。XQuery 可以访问 XML 数据(包括联结)、循环、声明的变量、IF/THEN/ELSE 语句和其他结构。
在 DB2 9 中有四种处理 XML 数据的方式:
- 一般 SQL
-
一般 SQL 由常规 SQL 语句和函数组成,它们没有引用 XPath 或 XQuery。一般 SQL 只能将 XML 文档作为完整的实体进行操作。可以在数据库中插入和检索 XML 文档,以及在实体和文本之间进行转换。但是,无法解析 XML 文档的一部分,也无法应用任何谓词。
- 带嵌入 XQuery 的 SQL/XML
- SQL/XML 包含三个操作 XML 数据的新函数,这些函数以嵌入的 XQuery 命令作为参数 —— XMLQUERY、XMLEXISTS 和 XMLTABLE。
- XMLQUERY 用来从 XML 数据中提取信息。通过结合使用 XMLCAST 函数,可以从 XML 中派生出关系数据。
- XMLEXISTS 对 XML 数据应用谓词,常常用在 WHERE 子句中。
- XMLTABLE 以关系表的形式提取 XML 数据。
- XQuery
- XQuery 现在是 DB2 数据库引擎中的本机特性。有两个用来访问 DB2 数据的函数。第一个函数 db2-fn:xmlcolumn 向 XQuery 返回 XML 列的值。包含所有行;没有谓词。
- 带嵌入 SQL 的 XQuery
- 还提供 db2-fn:sqlquery 函数来访问 DB2 数据,它以一个 SELECT 语句作为参数。这个 SELECT 可以执行所需的任何操作,但是必须在 SELECT 列表中返回一个 XML 类型的列。
存储过程中支持的 XML 功能
对于 DB2 9,SQL 存储过程有一些用来处理 XML 数据的新功能。有一种新的数据类型 “XML”。不但可以将它用于表中的列,还可以用于参数和声明的变量。DB2 9 还增强了 SELECT 和 UID 语句,增加了几个新的 XML 函数。
这在三个方面增强了 SQL 存储过程:
- 可以在以下位置使用新的 XML 函数(例如,XMLQUERY、XMLEXISTS、XMLTABLE):
- 在游标中
- 允许 SELECT 语法的任何地方,比如 SELECT INTO 和 VALUES
- 变量赋值,比如 SET 语句
- XML 列可以:
- 在游标结果集中返回
- 绑定(?表示法)到 SELECT 和 UID 语句中
- IF/THEN/ELSE 语句可以测试 XMLEXISTS 谓词
- 可以从以下语句装载 XML 类型的变量:
- SELECT INTO 语句
- VALUES INTO 语句
- FETCH INTO 语句
- EXECUTE INTO 语句
- SET 语句
限制和解决方法
SQL 存储过程中的 XML 功能有一些限制:
- XQuery 查询只能作为动态游标的一部分运行。
- XML 变量在 COMMIT 或 ROLLBACK 之后不可用。
动态游标中的 XQuery
XQuery 命令可以出现在两种不同的上下文中:
在 COMMIT 或 ROLLBACK 之后的 XML 变量
XML 变量(包括参数和声明的变量)在 COMMIT 或 ROLLBACK 之后不可用。如果试图使用 XML 变量,而没有首先给它们赋新值,就会产生下面这样的错误:
清单 3. 在 COMMIT/ROLLBACK 之后收到的错误消息
SQL1354N An SQL variable in routine "XXX" is not available for reference due
to a recent commit or rollback operation. SQLSTATE=560CE
|
如果需要在存储过程中显式地控制提交点,那么考虑以下选项:
- 将变量的 XML 文档传递进一个持久性的表中。使用 “持久性的” 表是因为声明的全局临时表当前不允许 XML 列。
- 将 XML 变量串行化成 VARCHAR,在 COMMIT 或 ROLLBACK 之后再解析它。
代码示例
我们来研究一下如何在 DB2 存储过程中处理 pureXML 数据。为此,我们将考虑几个与常见编程场景相关的示例。
生成测试数据
尽管在开发期间可以从命令行测试存储过程,但是可能需要创建虚构的 XML 文档,它们将作为输入参数发送。清单 4 提供一种生成测试数据的简单技术:
清单 4. 生成测试数据(1)
CALL ex_proc
(
XMLPARSE(
DOCUMENT
'
<customer>
<custid>HX25</custid>
</customer>
'
)
)
;
|
为了清晰,前一个示例保留了许多空白,使 XML 标记处于单独的行上。但是,也可以让代码更紧凑:
清单 5. 生成测试数据(2)
CALL ex_proc(XMLPARSE(DOCUMENT '<customer><custid>HX25</custid></customer>'));
|
声明参数
新的 XML 数据类型可以作为参数传递进存储过程中,可以采用任何模式(输入、输出或者同时作为输入/输出)。在声明数据类型为 XML 时,不指定长度。与将 XML 数据作为 CLOB 或 VARCHAR 处理(这需要指定最大长度)相比,这是一项不错的改进。
清单 6. 声明参数
CREATE PROCEDURE process_cust
(
IN inpdoc XML,
OUT outdoc XML,
INOUT doc3 XML
)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
...
END
|
声明变量
变量可以声明为 XML 类型。与参数的情况一样,不指定长度。
清单 7. 声明变量
CREATE PROCEDURE ex_proc
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
DECLARE v_xml XML;
...
END
|
变量赋值
除了从游标获得数据之外,可以使用三种不同的技术将来自单行查询的值装载进变量中:
- SET 语句
- SELECT INTO
- VALUES INTO
这三种技术都可以使用 SQL/XML 函数,尤其是 XMLQUERY。本节中后面的示例分别演示这些技术。
下面的所有示例都使用同样的输入/输出参数列表、同样的输入数据和同样的 SQL/XML 函数,并产生同样的结果。将三个参数传递给存储过程。第一个参数是一个输入 XML 文档。第二个参数是一个输出 VARCHAR。第三个参数是一个输出 XML 文档。
这些示例:
- 演示如何使用 XMLCAST,在将 XML 数据提取为标准关系数据类型(例如,CHAR 或 INT)时常常使用这种方法。
- 演示将 XMLQUERY 的输出赋值给 XML 类型的变量。
- 在 XMLQUERY 函数中,“PASSING inpdoc” 引用变量。如果在 SELECT 语句中使用 XMLQUERY,那么 PASSING 子句可以引用表列或者存储过程变量。
- 在调用存储过程的输出中,可以看到提取 XML 元素与提取 XML 元素值之间的差异。
- 尽管这个示例只将值赋给输出参数,但是使用声明的变量也很容易。
清单 8.
三个变量赋值示例的输入参数 “inpdoc” 的示例数据
<customer>
<name>Jed Clampett</name>
<city>Beverly Hills</city>
</customer>
|
清单 9.
三个变量赋值示例的输出
Value of output parameters
--------------------------
Parameter Name : P2
Parameter Value : Jed Clampett
Parameter Name : P3
Parameter Value : <name>Jed Clampett</name>
Return Status = 0
|
变量赋值 / SET 语句
我们首先考虑如何将 XMLQuery 函数的输出返回给存储过程中声明的变量。在这里,p2 将输出转换为 VARCHAR 值,而 p3 以 XML 形式返回输出。
清单 10.
使用 SET 语句进行变量赋值
CREATE PROCEDURE ex_proc
(
IN inpdoc XML,
OUT p2 VARCHAR(30),
OUT p3 XML
)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
SET p2 =
XMLCAST
(
XMLQUERY('$d/customer/name' PASSING inpdoc AS "d")
AS VARCHAR(30)
);
SET p3 = XMLQUERY('$d/customer/name'PASSING inpdoc AS "d");
END
|
变量赋值 / SELECT INTO
这个示例与前一个示例很相似。但是,这个版本使用完整的 SQL/XML SELECT 语句填充 p2 和 p3 变量。
清单 11. 使用 SELECT INTO 进行变量赋值
CREATE PROCEDURE ex_proc
(
IN inpdoc XML,
OUT p2 VARCHAR(30),
OUT p3 XML
)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
SELECT XMLCAST
(
XMLQUERY('$d/customer/name' PASSING inpdoc AS "d")
AS VARCHAR(30)
)
INTO
p2
FROM
sysibm.sysdummy1
;
SELECT XMLQUERY('$d/customer/name' PASSING inpdoc AS "d")
INTO
p3
FROM
sysibm.sysdummy1
;
END
|
变量赋值 / VALUES INTO
还可以使用 VALUES 子句填充变量 p2 和 p3,如下例所示。
清单 12. 使用 VALUES INTO 进行变量赋值
CREATE PROCEDURE ex_proc
(
IN inpdoc XML,
OUT p2 VARCHAR(30),
OUT p3 XML
)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
VALUES XMLCAST
(
XMLQUERY('$d/customer/name' PASSING inpdoc AS "d")
AS VARCHAR(30)
)
INTO
p2
;
VALUES XMLQUERY
(
'$d/customer/name' PASSING inpdoc AS "d"
)
INTO
p3
;
END
|
类型转换错误
如果目标数据类型与实际数据不兼容,XMLCAST 函数会引发错误。例如,不能将字符串 “HX25” 转换为整数。存储过程可以捕获类型转换错误,如清单 13 到清单 15 所示:
清单 13. 类型转换错误 —— 输入参数 “inpdoc” 的示例数据
<customer>
<custid>HX25</custid>
</customer>
|
清单 14. 类型转换错误
CREATE PROCEDURE ex_proc ( IN inpdoc XML )
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
DECLARE v_int INTEGER;
DECLARE v_badnum CHAR(1) DEFAULT 'N';
DECLARE CONTINUE HANDLER FOR SQLSTATE '10608'
SET v_badnum = 'Y';
SET v_int =
XMLCAST
(
XMLQUERY('$d/customer/custid' PASSING inpdoc AS "d")
AS INT
);
IF (v_badnum = 'Y') THEN
SIGNAL SQLSTATE '75002' SET MESSAGE_TEXT = 'Bad number';
END IF;
END
|
清单 15. 类型转换错误 —— 输出
SQL0438N Application raised error with diagnostic text: "Bad number".
SQLSTATE=75002
|
IF/THEN/ELSE 语句和 XMLEXISTS
可以使用 XMLEXISTS 在 IF/THEN/ELSE 语句中测试 XML 类型的变量。
清单 16. 涉及 IF/THEN/ELSE 的代码示例
CREATE PROCEDURE ex_proc(IN inpdoc XML)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
IF XMLEXISTS('$d/request[@action="add"]' PASSING inpdoc AS "d") THEN
SIGNAL SQLSTATE '75002' SET MESSAGE_TEXT = 'F(x) add';
ELSEIF XMLEXISTS('$d/request[@action="update"]' PASSING inpdoc AS "d") THEN
SIGNAL SQLSTATE '75002' SET MESSAGE_TEXT = 'F(x) update';
ELSE
SIGNAL SQLSTATE '75002' SET MESSAGE_TEXT = 'F(x) invalid';
END IF;
END
|
清单 17. IF/THEN/ELSE,输入值(1)
<request action="add">
<name>Jean Luc Picard</name>
</request>
Output:
SQL0438N Application raised error with diagnostic text: "F(x) add".
SQLSTATE=75002
|
清单 18. IF/THEN/ELSE,输入值(2)
<request action="update">
<name>Jean Luc Picard</name>
</request>
Output:
SQL0438N Application raised error with diagnostic text: "F(x) update".
SQLSTATE=75002
|
清单 19. IF/THEN/ELSE,输入值(3)
<request action="delete">
<name>Jean Luc Picard</name>
</request>
Output:
SQL0438N Application raised error with diagnostic text: "F(x) invalid".
SQLSTATE=75002
|
PASSING 子句
XMLQUERY、XMLEXISTS 和 XMLTABLE 函数的 PASSING 子句可以传递多个变量。变量不必限于 XML 数据类型。下两个示例演示两种有意义的操作。
清单 20. 测试属性值
CREATE PROCEDURE ex_proc
(
IN inpdoc XML,
IN search VARCHAR(20),
OUT id VARCHAR(30)
)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
SET id =
XMLCAST(
XMLQUERY('$d/customers/customer[@id=$a]/name' PASSING inpdoc AS "d",
search AS "a"
)
AS VARCHAR(30)
)
;
END
|
另一个示例演示如何使用 XMLQUERY(和其他 SQL/XML 函数)的 PASSING 子句。如果需要编写泛型代码以便访问以各种方式进行格式化的 XML 数据,那么可以根据位置(第一个、第二个、第三个等等)访问节点和属性。
输入参数 “inpdoc” 的示例数据
清单 21. 根据位置提取节点和属性 —— 输入数据
<order id="333" status="open">
<customer>
<custid>11029></custid>
<name>Johnny Depp</name>
<city>Hollywood</city>
</customer>
</order>
|
清单 22. 根据位置提取节点和属性
CREATE PROCEDURE ex_proc
(
IN inpdoc XML,
IN attrpos INT,
OUT attrname VARCHAR(30),
OUT attrvalue VARCHAR(30),
IN nodepos INT,
OUT nodename VARCHAR(30),
OUT nodevalue VARCHAR(30)
)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
VALUES
(
XMLCAST(
XMLQUERY('$d/order/@*[$a]/name()' PASSING inpdoc AS "d", attrpos AS "a")
AS VARCHAR(30)
),
XMLCAST(
XMLQUERY('$d/order/@*[$a]' PASSING inpdoc AS "d", attrpos AS "a")
AS VARCHAR(30)
),
XMLCAST(
XMLQUERY('$d/order/customer/*[$n]/name()' PASSING inpdoc AS "d", nodepos AS "n")
AS VARCHAR(30)
),
XMLCAST(
XMLQUERY('$d/order/customer/*[$n]' PASSING inpdoc AS "d", nodepos AS "n")
AS VARCHAR(30)
)
)
INTO
attrname,
attrvalue,
nodename,
nodevalue
;
END
|
清单 23. 根据位置提取节点和属性 —— 输出
Output
Value of output parameters
--------------------------
Parameter Name : ATTRNAME
Parameter Value : status
Parameter Name : ATTRVALUE
Parameter Value : open
Parameter Name : NODENAME
Parameter Value : city
Parameter Name : NODEVALUE
Parameter Value : Hollywood
Return Status = 0
|
改变 XML 文档的结构
这个示例演示如何改变 XML 文档的结构。原来的文档作为输入参数传递进存储过程,新文档作为输出参数传递出来。
清单 24. 输入和希望的输出
<customer> <customer @id="xxx">
<id> ... </id> <name>
<name> <first> ... </first>
<first> ... </first> <last> ... </last>
<last> ... </last> </name>
</name> <city> ... </city>
<city> ... </city> </customer>
</customer>
|
在清单 24 中,要将一个节点转换为属性。这是一项非常小的改动,但是可以应用同样的技术进行更复杂的改动。基本思想是将输入文档分解成单独的成分。如果一个成分无需修改,比如这个示例中的 <name> 元素,就保持 XML 类型,不做任何进一步的解析。如果重构涉及属性和节点之间的转换,就需要将数据转换为关系数据类型。
清单 25. 改变 XML 文档的结构
CREATE PROCEDURE ex_proc
(
IN inpdoc XML,
OUT outdoc XML
)
LANGUAGE SQL
NO EXTERNAL ACTION
BEGIN
DECLARE v_id INT;
DECLARE v_name XML;
DECLARE v_city XML;
VALUES
(
XMLCAST
(
XMLQUERY('$d/customer/id' PASSING inpdoc AS "d")
AS INT
),
XMLQUERY('$d/customer/name' PASSING inpdoc AS "d"),
XMLQUERY('$d/customer/city' PASSING inpdoc AS "d")
)
INTO
v_id,
v_name,
v_city
;
SET outdoc =
XMLQUERY
(
'<customer id="{$id}">
{$name}
{$city}
</customer>
'
PASSING v_id AS "id",
v_name AS "name",
v_city AS "city"
)
;
END
|
清单 26. 输入和输出值
输入参数 “inpdoc” 的示例数据
<customer>
<id>3005302</id>
<name>
<first>Miguel</first>
<last>Rodriguez</last>
</name>
<city>Wichita</city>
</customer>
Output:
<customer id="3005302">
<name>
<first>Miguel</first>
<last>Rodriguez</last>
</name>
<city>Wichita</city>
</customer>
|
结束语
在本文中,我们讨论了如何在 SQL 存储过程中使用 DB2 9 新的 XML 功能。在这个版本中引入了新的 XML 数据类型,可以通过 SQL/XML 函数和 XQuery 访问这种数据。本文中的示例演示了如何使用 XML 变量进行声明、赋值和测试。还学习了如何生成测试数据以及改变现有 XML 文档的结构,希望这对您的实际开发有帮助。希望本文提供的信息可以帮助 SQL 开发人员将 XML 功能结合进应用程序中。
术语表
游标(CURSOR):这种编程技术使应用程序能够逐行获得查询的结果。通常用一个循环连续获取每一行,直到到达数据的末尾。
动态 SQL(DYNAMIC SQL):所有查询分成 “动态” 或 “静态” 两种。“动态” 意味着查询作为字符串发送给数据库引擎;必须对它进行解析,并生成查询执行计划。从命令行处理程序(CLP)运行的任何查询都是动态的。
参数标志(PARAMETER MARKER):应用程序可能希望多次调用同一个查询,每次调用时只改变特定的值(例如,发送给存储过程的参数,或者 WHERE 子句中的值)。参数标志是满足这种需要的一种方式。查询在不希望使用硬编码值的地方使用 “?”。对查询进行准备,生成执行计划。每次执行查询时,提供的变量映射到查询的 “?” 部分。
静态 SQL(STATIC SQL):所有查询分成 “动态” 或 “静态” 两种。“静态” 查询只存在于应用程序(比如 SQL 存储过程)和应用程序中嵌入的 SQL(比如 Java 的 SQLJ)。静态查询已经经过解析,并生成了查询计划。计划作为 “包” 的一部分存储在数据库中。当执行静态查询时,会使用以前生成的查询计划。
UID:UPDATE、INSERT 或 DELETE 类型的 SQL 语句的首字母缩写。因为这三种语句都会修改数据,所以用这个首字母缩写作为它们的统称。
VALUES INTO:能够只对一行进行查询是有意义的。在 DB2 中,可以从 SYSIBM.SYSDUMMY1 表中进行选择,这个表只包含一行。还可以使用 “VALUES” 语法。这对于不从任何表或视图进行读取的查询是有意义的。VALUES INTO 将值存进变量中。
XML 属性(XML ATTRIBUTE):例如,在 XML 示例 <cust id="725"></cust> 中,有一个称为 “id” 的属性,它的值为 “725”。属性属于节点 —— 在这个示例中,属于节点 “cust”。与节点不同,属性必须具有惟一的名称。
XML 节点(XML NODE):XML 节点是开始标记和结束标记之间的所有东西,例如 <cust> blah blah blah </cust>。节点可以包含文本、属性、其他节点(因此形成 XML 的层次结构)以及其他一些特殊的东西(比如处理指令和注释)。
XMLCAST:这个新的 SQL 函数将 XML 节点的值转换为关系数据类型,比如 VARCHAR、INTEGER、DATE 等等。
XMLEXISTS:这个新的 SQL 函数根据某一条件测试 XML 文档。条件用 XQuery 语法来表示。它们可以测试特定节点是否存在、一个节点是否具有特定的值、特定的属性是否存在、属性是否具有特定的值等等。
XMLQUERY:这个新的 SQL 函数从 XML 文档中提取某些内容。这些内容可以是一个节点、一系列节点或者 count() 等 XQuery 函数。
XMLTABLE:这个新的 SQL 函数用来以关系表的形式查看 XML 文档的某一部分。
XQUERY:用来查询 XML 文档的本机语言。XQuery 最简单的部分称为 “XPath”。它看起来就像完全限定的 UNIX 文件名(比如 /abc/xxx/305xk),是一种用来表示谓词的语法。
致谢
我希望感谢对本文进行审阅并提供宝贵意见的所有人(按照字母排序):Merla Black、Eugene Firyago、Bill Gates、Strother Hollingsworth、Matthias Nicola、Don Payne、Cindy Saracco 和 Hardeep Singh。
参考资料 学习
获得产品和技术
-
下载 DB2 Enterprise 9 的免费试用版本。
-
现在可以免费使用 DB2。下载 DB2 Express-C,这是为社区提供的 DB2 Express Edition 的免费版本,它提供了与 DB2 Express Edition 相同的核心数据特性,为构建和部署应用程序奠定了坚实的基础。
-
下载 IBM 产品评估版,体验这些来自 DB2、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
-
使用 IBM 试用软件 构建您的下一个开发项目,这些软件可以从 developerWorks 直接下载。
讨论
关于作者  | |  | Rex Oliva 是在 Center of Excellence 从事 North American Lab Services 的 IBM DB2 顾问。他从 90 年代中期开始接触关系数据库,从 2000 年开始从事 DB2 方面的工作。他在数据仓库、执行 ETL(提取、转换和装载)、数据库设计、SQL 调优和重写以及为 DB2 应用程序开发人员提供协助方面具有丰富的经验。 |
对本文的评价
|