虽然 DB2 的混合体系结构与之前的版本有很大的不同,但是要利用它的新 XML 功能并不难。如果您已经熟悉 SQL,那么很快就可以将这方面的技能转化到对存储在 DB2 中的本地 XML 数据的处理上。通过本文就可以知道如何实现这一点。
@Msz KB@,_rj DB2 Viper(现在处于 beta 阶段)中的 XML 特性包括新的存储管理、新的索引技术以及对查询语言的支持。在本文中,学习如何使用 SQL 或 带 XML 扩展的 SQL(SQL/XML)查询 DB2 XML 列中的数据。接下来的文章将讨论 DB2 中新引入的对新兴的业界标准 XQuery 的支持,并探索 XQuery 在什么时候最有用。
gl%>49 u~E0* 您也许会感到惊讶,DB2 还支持双语查询 —— 即组合了来自 SQL 和 XQuery 的表达式的查询。至于应该使用哪种语言(或两种语言结合使用)取决于应用程序的需要,同时也取决于您本身所掌握的技能。其实,将两种查询语言中的元素组合到一个查询中并没有您想像的那么难。这样做还可以为搜索和集成传统 SQL 和 XML 数据提供强大的能力。
\-L!{ >|"!F}gi Sample 数据库
z
Oh*{?B> E,Xn\wzv 本文中的查询将访问在 “DB2 Viper 快速入门”(developerWorks,2006 年 4 月)中创建的 sample 数据库。这里我们简短地回顾一下,sample 数据库中 "items" 和 "clients" 表的定义:
g's&)^?2'H u%30x5JV- qp!9Jb"jH w#/p;'l7 清单 1. 表的定义
#5Q)(!ko GLC8p`~ create table items (
LoO+"!+[R id int primary key not null,
](s5$w&Q brandname varchar(30),
-(|zX^ itemname varchar(30),
FmKj%7^ sku int,
B-A(# srp decimal(7,2),
y"3% T{ comments xml
RUhw 7 ~m )
)a>s. create table clients(
^"~AYMv2 id int primary key not null,
Wv9S WYN p name varchar(50),
C=oFu?o status varchar(10),
fs0ke]@[ contactinfo xml
dB!-3
2 )
A\ 6p%*Q OOo8GT+@
,4f./D\
-,pT[2D 图 1 显示了 "items.comments" 列中的示例 XML 数据,图 2 显示了 "clients.contactinfo" 列中的示例 XML 数据。随后的查询例子将引用其中某个 XML 文档或这两个文档中某些特定的元素。
*6dC6tW +E.jUrFk3c mc~F~
& xZAI
kxF
J 图 1. 存储在 "items" 表 "comments" 列的示例 XML 文档
<Mq4p.69: <$8Y*
P
AeFe9rV ByiL
JG 图 2. 存储在 "clients" 表 "contactinfo" 列中的示例 XML 文档
JV{8$35 hd*IQ Y|BfP*9 (:).#z> x Ag0j%a~ @5Qp:n<n f#uL =- L{$ r1[^;D g7
>D2
AhL <1M?JJfO
y%fJ`C! >=G]k 查询环境
Vgs:7 sy-##
F[ 本文中的所有查询都是交互式地发出的,您可以通过 DB2 命令行处理器或 DB2 Control Center 中的 DB2 Command Editor 发出查询。本文中的屏幕图像和说明主要基于后一种方式。(DB2 Viper 还附带了一个基于 Eclipse 的 Developer Workbench,它可以帮助程序员图形化地构造查询。但是,本文不讨论应用开发问题或 Developer Workbench。)
jxkC >{b i&O :[A 要使用 DB2 Command Editor,需启动 Control Center 并选择 Tools > Command Editor。这时将弹出如 图 3 所示的窗口。在上面的面板中输入查询,单击左上角的绿色箭头运行查询,然后在下面的面板或 "Query results" 标签页中查看输出。
Uf3ji,*-n R:Fv6 ])r dBr~gZ &8OKax 图 3. DB2 Command Editor,可以从 DB2 Control Center 启动
S9&po~e w(3
x:no* 8k>FzknKH% ^KP+z9%S
K{jxdnG Y}
{G1dpXC NCmz[[qV ;yP|CPi> qn4.aQ' v2JIHj0% %#b5YEI^S ysa@pD 纯 SQL 查询
?ooT5;x B +(_naub 即使您对 SQL 所知有限,也仍然可以很轻松地查询 XML 数据。例如,下面的查询选择 "clients" 表中的全部内容,包括存储在 "contactinfo" 列的 XML 信息:
<|xup)zKx* g-GM4G+ j<lLP` oX+XxZrI 清单 2. 简单的 SELECT 语句
p:R1ke*y x6|A! select * from clients
2!P^h K["@,qVa *bhO[T]2&i Hw X-e 当然也可以编写更具选择性的 SQL 查询,使之包含关系投影和限制操作。下面的查询检索所有具有 "Gold" 状态的客户的 ID、姓名和联系方式。请注意,"contactinfo" 列包含 XML 数据,而其他两列不包含 XML 数据:
92 ,w_6}A DSE{ v[5& 1oWO<`[ :mEOU~kM 清单 3. 带投影和限制的简单 SELECT 语句
9>+%S
J(L uW$+**po select id, name, contactinfo
ixs6SCj[% from clients
Y1fy;p where status = 'Gold'
K098[n' Q[P%+rN DMo>}p>F e/0Z&bQ 正如您所预料,您可以基于这样的查询创建视图,下面的 "goldview" 可以说明这一点:
:_o$}&iF4 wEu]At2D /5H0Ze 9Lz.04J6O 清单 4. 创建包含 XML 列的视图
J;HdQ81{) 8gcpR{#r create view goldview as
8l3X5w) select id, name, contactinfo
xCl9d<S(M from clients
~%Ba^ > where status = 'Gold'
QHbd@_ FtbfMx OGy(5B~ p{J 4[o 不幸的是,很多事情光用 SQL 是无法解决的。通过纯 SQL 语句可以检索整个 XML 文档(刚才已证明这一点),但是却不能指定基于 XML 的查询谓词,也不能检索 XML 文档的某一部分或者 XML 文档中特定的元素值。换句话说,使用纯 SQL 不能对 XML 文档中的片段进行投影、限制、连接、聚集或排序操作。例如,您不能单独检索 Gold 客户的 email 地址或居住在邮政编码为 "95116" 的地区的客户的姓名。为了表达这些类型的查询,需要使用带 XML 扩展的 SQL(SQL/XML)、XQuery 或结合使用这两种查询语言。
2ziBe yEOr8mAIl 下一节将探讨 SQL/XML 的几个基本特性。在接下来的文章中,我们将学习如何编写 XQuery 以及如何将 XQuery 与 SQL 结合使用。
r
Gz3~ #]#h&Zu 6'ahgTT 3hb(-Vpt AMx.*%|O1 krpi1K.
fKQSm 3 oT8~uP (la&@yd(X ELJO6Fb$z 1 .A Depm& brzjQH@ SQL/XML 查询
K]X|*pg0 <^ra(rg] 顾名思义,SQL/XML 被设计用来为 SQL 和 XML 两者之间搭一座桥。它首先是 SQL 标准的一部分,经过演化现在包括将 XQuery 或 XPath 表达式嵌入 SQL 语句的规范。XPath 是用于导航 XML 文档以便发现元素或属性的一种语言。XQuery 包括对 XPath 的支持。
yU~wZL(` zzDFRLA=D8 请务必注意,XQuery(和 XPath)表达式是大小写敏感的。例如,引用 XML 元素 "zip" 的 XQuery 并不适用于名为 "ZIP" 或 "Zip" 的 XML 元素。SQL 程序员有时候很难记住大小写敏感这一点,因为 SQL 查询语法允许使用 "zip"、"ZIP" 和 "Zip" 来引用同一个列名。
fq)dk"R7 k).o$Qcf DB2 Viper 提供了超过 15 个 SQL/XML 函数,通过这些函数可以搜索 XML 文档中的特定数据,将传统数据转换成 XML,将 XML 数据转换成关系数据,以及执行其他有用的任务。本文不讨论 SQL/XML 的所有方面,而只是谈到几种常见的查询挑战,以及一些关键的 SQL/XML 函数如何解决这些挑战。
&pm43<QL ) g ^2h 根据 XML 元素值 “限制” 结果
Yc;o S~$ F]'0LR SQL 程序员常常编写根据某种条件限制从 DBMS 返回的行的查询。例如,清单 3 中的 SQL 查询限制从 "clients" 表中检索的行,使之只包括那些具有 "Gold" 状态的客户。在这个例子中,客户的状态可在 SQL VARCHAR 列中捕捉。但是,如果您想根据某种应用于 XML 列中数据的条件对搜索进行限制,那么应该怎么做呢?SQL/XML 的 XMLExists 函数为完成该任务提供了一种手段。
f ck`{Q8Y c1, pvB< 通过 XMLExists 可以在 XML 文档中找到一个元素,并测试它是否满足某个特定的条件。如果用在 WHERE 子句中,则 XMLExists 可以限制返回的结果,使之只包括那些包含具有特定 XML 元素值的 XML 文档的行(换句话说,指定的值等于 "true")。
r4\#iADqN #j;d'
P| 让我们看看早先遇到的一个查询问题。假如您想找到居住在具有特定邮政编码的地区的所有客户的姓名。您也许还记得,"clients" 表的一个 XML 列中存储了客户的地址(包括邮政编码)。(见 图 2。)通过使用 XMLExists,可以从 XML 列中搜索目标邮政编码,并相应地限制返回的结果集。下面的 SQL/XML 查询返回居住在邮政编码为 95116 的地区的客户的姓名:
6AH%0F 7"}v#Srq |eM~6[Vn ~JKF<niv?? 清单 5. 根据 XML 元素值限制结果
K- .2dQr_ xN6np84Z select name from clients
N .^J[7l where xmlexists('$c/Client/Address[zip="95116"]'
p"& #'CV passing clients.contactinfo as "c")
l-ya&/o5v $pF<3l
i~ @4EVO6 , vG')`uMq 第一行是一个 SQL 子句,指定仅检索 "clients" 表 "name" 列中的信息。WHERE 子句调用 XMLExists 函数,指定一个 XPath 表达式,这个表达式使 DB2 找到 "zip" 元素并检查元素值是否为 95116。"$c/Client/Address" 子句表明 DB2 用于定位 "zip" 元素的 XML 文档层次结构中的路径。通过使用可以从节点 "$c"(稍后将会解释)访问的数据,DB2 将从 "Client" 元素中找到它的子元素 "Address",以便检查邮政编码("zip" 值)。最后一行决定 "$c" 的值:它是 "clients" 表的 "contactinfo" 列。因此,DB2 检查 "contactinfo" 列中的 XML 数据,从根元素 "Client" 深入到 "Address" 子元素再找到 "zip" 子元素,然后判断客户是否居住在目标地区。如果客户住在目标地区,则 XMLExists 函数的返回值为 "true",DB2 将返回与那一行相关的客户的姓名。
7[/5imp=
RA+% 在使用 XMLExists 查询谓词时经常会出现一个错误,如 清单 6 所示。
sz1h"Ze ?dw&CI #qB2, fu0U"Frd7 清单 6. 根据 XML 元素值限制结果时采用的不正确语法
r1Hx-q vkoI 7G select name from clients
u g[XyT where xmlexists('$c/Client/Address/zip="95116" '
`@/2x passing clients.contactinfo as "c")
D^^|kk |nwAza*u )0vd] . b{-2p%ve8 虽然这个查询也可以成功地执行,但是它不能限制结果,使之仅包含居住在邮政编码为 95116 的地区的客户。(这是由于标准中规定的语义造成的,而且这并不是 DB2 所特有的。)为了限制结果,使之仅包含居住在邮政编码为 95116 的地区的客户,需要使用前面 清单 5 中展示的语法。
a>qV*6u /"=}!U;^ 您可能很想知道如何在应用程序中包括限制 XML 数据的查询。虽然本文不详细讨论应用开发话题,但还是提供了一个 简单的 Java 例子,这个例子在一条 SQL/XML 语句中使用一个参数标记位将输出限制为居住在给定地区的客户的信息。
d!$)Wm uu{+- 50S “投影” XML 元素值
Oa3R"Z. RLcM|E? 现在让我们考虑一种稍微有些不同的情景,假设您想将 XML 值投影到返回的结果集。换句话说,我们要从 XML 文档中检索一个或多个元素值。有很多方法可以做这件事。首先我们使用 XMLQuery 函数来检索一个元素的值,然后使用 XMLTable 函数来检索多个元素的值,然后将这些映射到一个 SQL 结果集的列。
m(uHxM<] y7=U7rq 我们来考虑如何解决之前摆在我们面前的一个问题:如何创建一个列出 Gold 客户的 email 地址的报告。下面 清单 7 中的查询调用 XMLQuery 函数来完成这项任务:
D@WlNN/9S #">R6:3 U.Bw'=7 JZhW>^g 清单 7. 检索符合条件的客户的 email 信息
3
0
J2zm 2ycTv5= select xmlquery('$c/Client/email'
va[G113 passing contactinfo as "c")
|gzI
(g from clients
z1IWnH[U_ where status = 'Gold'
Y/{&\cMIv !^Wh9 ;]Yc]n' \zOx%C/ 第一行指定要返回根元素 "Client" 的 "email" 子元素的值。第二行和第三行表明 DB2 在哪里可以找到该信息 —— 在 "clients" 表的 "contactinfo" 列中。第四行进一步限制查询,表明您只对 Gold 客户的 email 地址感兴趣。这个查询将返回一组 XML 元素和值。例如,如果有 500 名 Gold 客户,每个客户有一个 email 地址,那么输出将是一个单列的结果集,一共有 500 行,如 清单 8 所示:
-w "3C$ pL^/;x}%[ `7M5iGPU 5}@}
g 清单 8. 之前查询的示例输出
N
g#[^9 i_C
" 1
Eux|0F --------------------------------------------
. &DN(@ <email>
user5976@anyprovider.com</email>
;65%c|F . . .
|dBHNzi <email>
someID@yahoo.com</email>
aXVxUHO )I~~9bC Aob!6 PwOU{RH A 如果每个 Gold 客户有多个 email 地址,那么需要指示 DB2 只返回首要的地址(也就是在客户的 "contactinfo" 文档中找到的第一个 email 地址)。为此,可以修改查询的第一行中的表达式:
&W?I[j[T d6YQ;,o< Mh~JWD|) rKqAP+xD
清单 9. 检索每个符合条件的客户的第一个 email 地址
MDY
6G^ #n}QrE}C select xmlquery('$c/Client/email[1]'
8L>s ;Q8| passing contactinfo as "c")
]J{>e]LU from clients
.;X%fe_ where status = 'Gold'
/_k E dqBuPYNg Afh/ll`Sv `ZOKZwQ 最后,如果有些 Gold 客户没有 email 地址,那么可能要编写一个查询从结果集中排除 null 值。为此可以修改之前的查询,添加另一个谓词到 WHERE 中,以测试是否缺少 email 信息。您已经熟悉了一个可以帮您实现这一点的 SQL/XML 函数 —— 那就是 XMLExists。清单 10 展示了如何重新编写之前的查询,以便过滤掉那些联系方式(存储为 XML 文档)中缺少 email 地址的 Gold 客户的行:
L+~,syg 0 ^8H
z6% -AMc
:F =|;t=nxC 清单 10. 对于至少有一个 email 地址的客户,检索每个符合条件的客户的第一个 email 地址
`4wOWK .13hI_g select xmlquery('$c/Client/email[1]'
+:o`*~ passing contactinfo as "c")
MdA9u from clients
F\LoZ#"w&: where status = 'Gold'
+"G5WR
w<y and xmlexists('$c/Client/email' passing contactinfo as "c")
~UjsKUy5 v/CP9,V4 *t4BrV1/ Ix@%n: 现在我们考虑一个稍微不同的情景,假设您要检索多个 XML 元素值。XMLTable 可以从 XML 列中的数据生成标量输出,可以为程序员提供 XML 数据的 “关系” 视图,因此非常有用。与 XMLExists 和 XMLQuery 一样,XMLTable 函数使 DB2 在 XML 文档层次结构中定位到感兴趣的数据。然而,XMLTable 还包括一些子句,用于将目标 XML 数据映射到 SQL 数据类型的结果集列。
}IVtDcsv +SfW(=@WWh 考虑以下查询(清单 11),该查询投影存储在 "items" 表中的关系数据和 XML 数据。(关于 "items" 表请查看 图 1)。评论 ID、客户 ID 和评语存储在 "comments" 列中的 XML 文档中。商品名称存储在一个 SQL VARCHAR 列中。
F i-Xf 6m.Xl .viFK'4O qpWwnM@n 清单 11. 检索多个 XML 元素并将每个元素转换成传统的 SQL 数据类型
JVHN@|L Z=q;;p*;O select t.Comment#, i.itemname, t.CustomerID, Message from items i,
=
g!"QG0 xmltable('$c/Comments/Comment' passing i.comments as "c"
H&`[3fo. columns Comment# integer path 'CommentID',
YHRKgP;vP CustomerID integer path 'CustomerID',
L
V%$9? Message varchar(100) path 'Message') as t
/Y5upo Y
*2)aa/
<p7b' .FQ]BFZ W 第一行指定将包含在结果集中的列。查询中后面的几行表明,用引号括起来、并且以变量 "t" 为前缀的列是基于 XML 元素值的列。第二行调用 XMLTable 函数指定包含目标数据("i.comments")的 DB2 XML 列和在该列的 XML 文档中的路径,通过该路径可以定位感兴趣的元素(在根元素 "Comments" 的子元素 "Comment" 中)。第 3 到 5 行的 "columns" 子句标识出将被映射到第一行指定的 SQL 结果集中的输出列的特定 XML 元素。这个映射需要指定 XML 元素值将被转换成的数据类型。在这个例子中,所有 XML 数据被转换成传统的 SQL 数据类型。
SGw4 @Q Ce;$Oq~7 图 4 展示了运行该查询得到的示例结果。可以看到,输出是一个简单的 SQL 结果集。注意,列名已经被变成大写形式 —— 这在 SQL 中是很常见的。
fyQ_'1D03 #
r 1=P[ >Gbb
(h Vt}
%w 图 4. 使用 XMLTable 函数的查询的示例输出
>`-#o&sH m6N[M4]D\6 Mt[79`+U
如果需要的话,还可以使用 XMLTable 创建包含 XML 文档的结果集。例如,以下语句产生类似于上述结果的结果集,不同的是 "Message" 数据被包含在一个 XML 列中,而不是包含在一个 SQL VARCHAR 列中。
)I22 F #,< M
M]z2vKT u;ZOF`Q '<JIl~.E 清单 12. 检索多个 XML 元素并将它们转换成传统的 SQL 或 XML 数据类型
/+X:,bz+) >qg<@-D< select t.Comment#, i.itemname, t.CustomerID, Message from items i,
D~
1*qJMN* xmltable('$c/Comments/Comment' passing i.comments as "c"
XE]K"q*y columns Comment# integer path 'CommentID',
h- ,
y CustomerID integer path 'CustomerID',
`u&$ <W9*d Message XML by ref path 'Message') as t
2|4bFU3 .%!:?N90 DrZ`.H*F :hLuhMn 创建 XML 数据的关系视图
do T9*7Q =Mgbm|--
D 正如您可能想像到的那样,SQL/XML 函数可用于定义视图。如果要为 SQL 应用程序的程序员提供本地 XML 数据的关系模型,那么这种功能特别有用。
[znEtXE
n'xQ 为 XML 列中的数据创建关系视图并不比投影 XML 元素值复杂多少。您只需编写一个 SQL/XML SELECT 语句,在语句中调用 XMLTable 函数,并以此作为视图定义的基础。下面 清单 13 中的例子基于 "items" 表的 XML 列和非 XML 列中的信息创建一个视图。(这类似于 清单 11 中的查询。)
vp>:Tgd, Qs1aa A&c# "F*T']5!I 清单 13. 基于 XMLTable 的输出创建视图
$c`e\R9 X,"rX`0% create view commentview(itemID, itemname, commentID, message, mustrespond) as
(
{0d^|V( select i.id, i.itemname, t.CommentID, t.Message, t.ResponseRequested from items i,
j> /3j` xmltable('$c/Comments/Comment' passing i.comments as "c"
DpyX17cd columns CommentID integer path 'CommentID',
oL3_[^P Message varchar(100) path 'Message',
`0cvY>EUn ResponseRequested varchar(100) path 'ResponseRequested') as t;
5@|yRz7 xoVr;v{y JT'Of
'ppl:T6y 虽然在 XML 列中的数据上创建关系视图很容易,但是用起来要小心。在对那样的视图发出查询时,DB2 不使用 XML 列索引。因此,如果以 ResponseRequested 列为索引,并发出一条将 "mustrespond" 列的结果限制为某个特定值的 SQL 查询,那么 DB2 将读取所有的 XML 文档,并搜索适当的 "ResponseRequested" 值。除非数据量不大,否则这样做会降低运行时性能。然而,如果在那些视图上运行的查询还包含有严格限制性的谓词,且参与索引的项中有传统的 SQL 类型的项(在这个例子中可以是 "i.id" 或 "i.itemname"),那么可以缓解潜在的运行时性能问题。DB2 使用关系索引将符合条件的行过滤到一个较小的量,然后在返回最终结果之前,将更多的 XML 查询谓词应用到这些临时的结果上。
Q?M9bDl o2/pAmR 连接 XML 数据和关系数据
-BWv#z,a =]F'+%_l 现在,您可能想知道如何连接 XML 数据和非 XML 数据(例如基于传统 SQL 类型的关系数据)。DB2 使您可以仅用一条 SQL/XML 语句来做到这一点。有很多方法可用来制定那样的连接,这取决于数据库模式和工作负载需求,不过这里我们只谈论一个例子。您也许会感到惊讶,其实您已经知道足够多关于 SQL/XML 的东西,完全可以实现这种连接。
jIYLf0c}4 r.lH^k)g 还记得吗,"items" 表中的 XML 列包含一个 "CustomerID" 元素。这可以作为与 "clients" 表中基于整数的列 "id" 的一个连接键。因此,如果要获得一个报告,其中列出对您的一件或多件产品发表了评论的客户的姓名和状态,那么需要将一个表中的 XML 元素值与来自另一个表中的 SQL 整数值相连接。实现这一点的一种方法是使用 XMLExists 函数,如 清单 14 所示:
:P#J#!@1 SV`E\O;u Mb""o>o aZYwvy^i 清单 14. 连接 XML 数据和非 XML 数据
H[9!8w @
H GOk- \ select clients.name, clients.status from items, clients
\- hwL` where xmlexists('$c/Comments/Comment[CustomerID=$p]'
.,B+jj passing items.comments as "c", clients.id as "p")
1R,Llf7 :4X'C&V#vn _X{#iUV: *!y(K 第一行标识出要包括在查询结果集中的 SQL 列以及查询中所引用的源表。第二行包括了连接子句。这里,XMLExists 决定在一个目标源中的 "CustomerID" 值是否等于来自另一个目标源的值。第三行指定这两个源:第一个目标源是 "items" 表中的 XML 列 "comments",第二个目标源是 "clients" 表中的整数列 "id"。因此,如果客户对任何产品发表了评论,并且 "clients" 表中存在关于该客户的信息,那么 XMLExists 表达式将等于 "true",报告中将包括该客户的姓名和状态。
L`uMV hS:B8z} 使用 SQL/XML 中的 "FLWOR" 表达式
41}~S_AH tb Hm/l 虽然我们只讨论了几个函数,其实 SQL/XML 为查询 XML 数据和将 XML 数据与关系数据集成提供了很多强大的功能。实际上,您已经看到了这方面的一些例子,但是这里我们还要再讨论一些例子。
t.ytr$H" &!i["y
M 通过 XMLExists 和 XMLQuery 函数都可以将 XQuery 嵌入到 SQL 中。前面的例子展示了如何使用这些函数和简单的 XPath 表达式访问 XML 文档中感兴趣的某个部分。现在我们考虑一个简单的例子,这个例子将 XQuery 包括在 SQL 查询中。
I[O0b'|SG+ ~yC-;2K# XQueries 可以包含 "for"、"let"、"where" "、"order by" 和 "return" 子句中的一些或者全部。这些子句一起形成了 FLWOR (发音为 flower)表达式。SQL 程序员会发现,将 XQueries 嵌入到 SELECT 列表中以便将 XML 文档的片段提取(或投影)到结果集是很方便的。虽然 XMLQuery 函数的用法不止于此,不过本文只讨论这种情况。(将来的文章将更深入地讨论 XQuery。)
W1J?/BWo t V1HQHC$o~ 假设您要检索 "Gold" 客户的姓名和首要 email 地址。在某些方面,这个任务类似于我们前面在探索如何投影 XML 元素值的时候完成过的一个任务(参见 清单 9)。而在这里,我们将 XQuery (带有 "for" 和 "return" 子句)作为 XMLQuery 函数的输入:
0ADNC:#v& 1.xkm<=32l 8bQfqi! 3]^V
:wcr 清单 15. 使用 XQuery 的 "for" 和 "return" 检索 XML 数据
$"'orblR! g~=\j@y select name, xmlquery('for $e in $c/Client/email[1] return $e'
L\+b8*Fa] passing contactinfo as "c")
s+)FZ^}~ from clients
b6ka
A^l3 where status = 'Gold'
Lk-t*V wCuZ`Vq* Seao~2[l N2W<h 第一行指定结果集中将包括客户姓名和 XMLQuery 函数的输出。第二行表明将返回 "Client" 元素的第一个 "email" 子元素。第三行标识出 XML 数据的源 —— "contactinfo" 列。第四行说明这个列在 "clients" 表中,最后,第五行表明我们只对 "Gold" 客户感兴趣。
XTQ,Zuwk }
>ns\ 因为这个例子很简单,在这里您可以这样编写这个查询。不过,也可以用一种更紧凑的方式编写这个查询:
U+|$y9x, Z)'Dbh|WVz tWB"@"= j^"hj|tf 清单 16. 以更紧凑的方式重写查询
Lg4SB6Xu &?[ONS select name, xmlquery('$c/Client/email[1]'
A+D(#D;A passing contactinfo as "c")
02Fj2*~GA from clients
pa?6P
2x where status = 'Gold'
} 0~l<$p 5
mNFVO Ycbs
!K? jlDD/r0F_ 不过,通过 XQuery 的 return 子句可以按照需要转换 XML 输出。例如,您可以提取 email 元素值并将它们发布为 HTML。下面的查询将产生一个结果集,其中每个 Gold 客户的第一个 email 地址以 HTML 段落的形式返回。
=l|VWhy{i rMebAN=P3 Oz;D`XW (S/JKS 清单 17. 检索 XML 并将其转换成 HTML
(<I.M> 0{3=Tt select xmlquery('for $e in $c/Client/email[1]/text()
/+L,vdC return <p>{$e}</p>'
FrSsSkg passing contactinfo as "c")
9Kj0azz8o from clients
n]Mf0em where status = 'Gold'
NoH|j\ 4*.Nb0^ X&..5 X)6Iblakq 第一行表明您只对符合条件的客户的第一个 email 地址的文本表示形式感兴趣。第二行指定该信息在返回之前需要用 HTML 段落标记括起来。具体来说,花括号({ })指示 DB2 计算被括起来的表达式(在这里是 "$e")的值,而不是将其视作一个文字字符串。如果省略了花括号,对于每个符合条件的客户记录,DB2 将返回一个包含 "<p>$e</p>" 的结果。
])EOeOPE wt
J2J@iP 将关系数据发布为 XML
(YD~|y2> a@)rx2\h 到到目前为止,我们一直都在着重讨论查询、提取或转换存储在 DB2 XML 列中的数据的方法。而且您已经看到,这些功能都可以通过 SQL/XML 提供。
% 0=dM^+m ));p#Fa SQL/XML 还提供了其他非常方便的特性。其中一个特性是将关系数据转换或发布为 XML。本文只讨论这方面的三个 SQL/XML 函数:XMLElement、XMLAgg 和 XMLForest。
g 6?e\{= w!! ]u]b 通过 XMLElement 可以将存储在传统的 SQL 列中的数据转换成 XML 片段。也就是说,可以基于基本的 SQL 数据构造 XML 元素(带 XML 属性或者不带 XML 属性)。下面的例子嵌入了 XMLElement 函数来创建一系列的 item 元素,每个 item 元素包含一些子元素,分别存放从 "items" 表获得的 ID、品牌和库存单位("sku")值:
\~B{,'Hf:g wWQt\CmX4 s$>5kWs wQ7a 清单 18. 使用 XMLElement 将关系数据发布为 XML
L%Wf\
9 d FwW select xmlelement (name "item",
7uXmmT
0 xmlelement (name "id", id),
i\E<.o xmlelement (name "brand", brandname),
,<_4,1.E xmlelement (name "sku", sku) ) from items
Ij_AKz:I where srp < 100
10y0?@
~KH'UO] -eW_ICx}G \2xMX*/ 运行该查询将产生类似以下的结果:
Q.W<Xn% 6y*t6}qJ ^f4OGyH>a NCJTK+q 清单 19. 上述查询的示例输出
l;
2N ar@ ?G@.L) <item>
qcx(+aq' <id>4272</id>
_MJl"&-Z! <brand>Classy</brand>
2w(puaGs <sku>981140</sku>
0H@w/r:b </item>
(! L`]*5 . . .
Q=?DKB]0 <item>
JL}p/2" <id>1193</id>
ikCWN <brand>Natural</brand>
5#Q}!G <sku>557813</sku>
X4ZD]z jC </item>
DOQ$9x]2/ !pliW0VkG L- 8 F}TL))C/ 可以将 XMLElement 与其他 SQL/XML 发布函数结合使用来构造 XML 值以及将这些值分组,使它们嵌套成一定的层次结构。清单 20 中的例子使用 XMLElement 创建 customerList 元素,该元素的内容按照 "status" 列中的值分组。对于每个 "customerList" 记录,XMLAgg 函数返回一系列的 customer 元素,每个 customer 元素包含基于 "name" 和 "status" 列的子元素。而且可以看到,customer 元素的值是按照客户姓名排序的。
0[FFr1SB !r
!{$[ hAe/Uu@t n0x!pUi 清单 20. 聚集数据和对数据分组
HHq'>x/?w 6+s^or)f select xmlelement(name "customerList",
'wGE"~$ xmlagg (xmlelement (name "customer",
uJ V"jXd xmlforest (name as "fullName", status as "status") )
?0:8M(wQ% order by name ) )
Qbp
p~XM from clients
w` S_4X group by status
L@^
r):9 l<\V
o:c p
rGdb&} X|
2ZRe`Z 假设 "clients" 表包含三个不同的 "status" 值:"Gold"、"Silver" 和 "Standard"。运行上述查询将导致 DB2 返回三个 customerList 元素,每个 customerList 元素可能包含多个 customer 子元素,每个 customer 子元素又进一步包含姓名和状态信息。因此,输出将类似于以下内容:
i<Sm~}lm% _?tz &RV1~;,8 h6t@RrpR 清单 21. 上述查询的输出
QBZK'` [:W99#f2N <customerList>
CF%Yh$v]k <customer>
MOM_[1i <fullName>Chris Bontempo</fullname>
oT\ 8}x <status>Gold</status>
Sy|iW
7c </customer>
`/K!FB <customer>
0E]q^`D$ <fullName>Ella Kimpton</fullName>
kg
Ppk
<status>Gold</status>
F'MX\_X8\ </customer>
kER]umvK . . .
>`F2hzA </customerList>
\k]h xWR5 <customerList>
a{7wf$ <customer>
EhSm2:[C <fullName>Lisa Hansen</fullName>
hs
BM-4YL <status>Silver</status>
^W *{%5; </customer>
`-'Y{ . . .
+D,<tf </customerList>
ws7<CW
8 <customerList>
m&TDy.W\r$ <customer>
2;]"5P <fullName>Rita Gomez</fullName>
E*=[UK7 <status>Standard</status>
Q{&GgZa </customer>
HK]Y}-,b . . .
[$Qk </customerList>
y Q1O,f'sm nW+jT;f zwRtR_Rn
`+,*9Z}9i oCU_:-UNH 308.FRXu| tCa4LRU$nX (4K0@pO Nch e]8* i4zmz"V ?Z<afg;w. &3L%H%Iy PUOp?\I3 更新和删除操作
|a1d
}b y<0 t% 虽然本文的重点是使用 SQL 搜索和检索存储在 XML 列中的数据,不过这里仍然值得花一点时间考虑一下另外两项常见的任务:更新和删除 XML 列中的数据。
yJ
E%k Hj48l;% DB2 允许用户使用 SQL 和SQL/XML 语句更新和删除 XML 数据。实际上,由于 XQuery 标准的初稿没有解决这些问题,DB2 用户必须依赖 SQL 来完成这些任务。
Ry**9rq ["v{OP! 更新 XML 数据
z~>}|{q& $!-")$z DB2 允许用 SQL UPDATE 语句或通过使用系统提供的存储过程(DB2XMLFUNCTIONS.XMLUPDATE)来更新 XML 列。不管使用哪种方式,对 XML 列的更新都发生在元素级。然而,使用存储过程更新 XML 数据的程序员不需要提供整个 XML 文档给 DB2;他们只需指定要更新的 XML 元素。发出 UPDATE 语句的程序员则需要指定整个文档(而不仅仅是要更改的元素)。
e]H2Ghi
qapZ}NO 例如,如果要发出一条 UPDATE 语句来更改某个特定客户的联系方式信息中的 email 地址,就必须在 XML 列中提供全部联系方式信息,而不仅仅是新的 email 元素值。根据 图 2,提供的信息将包括 "Address" 信息、"phone" 信息、"fax" 信息和 "email" 信息。
D=je<A`} B\DiUk=; 考虑以下语句:
yoC5QN, (9l8u<']E- 5
C3)c4( <).VJPJ. 清单 22. 示例 UPDATE 语句
&pJO\!: fwmv4n^<( update clients set contactinfo=(
R{w'~Vi xmlparse(document '<email>
newemail@someplace.com</email>' ) )
l*5a+ where id = 3227
h&.5tDR N&5cX8Z
P >r#>
7^ gqO$.f<;;( 回忆一下在 “DB2 Viper 快速入门” 中我们是如何插入 XML 数据的,这里的语句大部分仍然是类似的。与任何 SQL UPDATE 语句一样,这个例子首先标识出要更新的表和列。由于目标列包含 XML 数据,因此需要提供一个格式良好的 XML 文档作为新的目标值。虽然大多数生产环境在应用程序中使用主机变量或参数标记位来更新 XML 数据,但是在这里我展示了用一种简单的方式来交互式地完成该任务。第二行使用 XMLParse 函数将输入字符串转换成 XML。对于 beta 版的 Viper,需要显式地调用 XMLParse。当 Viper 变得普遍可用时,显式调用应该只是成为一种选择。最后一行是一个标准的 SQL 子句,规定只更新表中特定的一行。
~T\OgHd J,R 9[B 如果执行上述 UPDATE 语句,则客户 3227 的 "contactinfo" 列将只包含 email 信息,如 清单 23 所示:
e*S:0=k dwO)5; 8Rxu]KCSI i+(C@[$ 清单 23. 执行上述 UPDATE 语句的效果
vu<n9!%a ]da4_Oq8 <email>
newemail@someplace.com</email>
z@?Q$g;1 .r>il.. J osd,B~A ag15Yww 这位客户的地址、电话号码和传真号码(如 图 2 所示)将丢失。而且,之前编写的用于提取客户的 email 地址的那些查询也无法恢复这些信息。为什么?之前的那些查询包括 XPath 或 XQuery 表达式,这些表达式在一个特定的文档层次结构中导航,而在这个结构中 Client 是根元素,email 是一个子元素。在像上面这样更新该文档之后,email 将变成这个客户的 XML 记录的根元素;因此,在这个层次结构中再也不能在预期的位置上找到它的值。
Ueu{j5 LA/E 如果要交互式地更新这个客户的 email 地址,并且保留所有其他已有的联系方式信息,应该像 清单 24 中那样重写查询:
uU%!VD: .a$r7 nVrH ^2 W{z
;fq4qjU{TZ 清单 24. 修改后的 UPDATE 语句
t!zW}Fj W[4Du[AQ* update clients set contactinfo=
Q/yN5e5T (xmlparse(document
BGi$iyA c '‘<Client>
f^:zM^h3CS <Address>
K!W <street>5401 Julio Ave.</street>
:97K 4j <city>San Jose</city>
p_
#o
P\` <state>CA</state>
p2js[D8sz <zip>95116</zip>
ofg!5DRSl </Address>
a'Jyg;8s <phone>
Yim7Hpz <work>4084633000</work>
C7$\&P{/ <home>4081111111</home>
#
9o)\4{ <cell>4082222222</cell>
1M8(
NWC </phone>
//uL%2% <fax>4087776666</fax>
iZ_AJ%'*a <email>
newemail@someplace.com</email>
}Tl
-!=< </Client>' ) )
c9\HBl&#U where id = 3227
RfeTjE&9 zDuf}aha0d VBrg33O1 2.g[2+; 也许您想知道是否可以通过一个视图进行更新,从而避免提供整个 XML 文档。例如,清单 13 中定义的 commentview 使用 XMLTable 函数提取 XML 文档中的某些元素,并将这些元素转换成视图中的 SQL 列。那么,是否可以更新这些 SQL 列中某个列的值,并将结果写回到初始的 XML 文档的适当子元素中呢?答案是否定的。在 DB2 中,基于 SQL 类型的视图列与从一个函数(在这里是 XMLTable 函数)得到的视图列是有区别的。对后者的更新不受支持。
G.^+e3ovE |F5$X 删除 XML 数据
7ppd ,(2/9I, 删除包含 XML 列的行很简单。SQL DELETE 语句允许通过 WHERE 子句识别(或限制)要删除的行。该子句可以包括简单的谓词来标识非 XML 列值或包括 SQL/XML 函数来标识包含在 XML 列中的 XML 元素值。
zlP2^]5 9L#zQuOIi 例如,下面展示了如何删除客户 ID 为 3227 的客户的所有信息:
_W=daq5 "(`wO.itm AfMu-+ ?q/j!X @ 清单 25. 删除一个特定客户的数据
x>.s@BI5 0=X7q4$ delete from clients
/avOtx where id = 3227
rm(c>&K.; X\!7v$k s y{6/{ '#FO 0v<p li 还记得怎样限制 SQL SELECT 语句,使之仅返回居住在邮政编码为 95116 的地区的客户的行吗?如果还记得的话,很容易知道如何删除与那些客户相关的行。下面看看如何使用 XMLExists 来做这件事:
6P16D;* J[ym6e7 N`Y"w ~
> }5bbf; 清单 26. 删除居住在特定地区的客户的数据
j.X^LOHhD --)`Yr0%m delete from clients
1)2 3A
where xmlexists('$c/Client/Address[zip="95116"]'
_2Z,7bV5 passing clients.contactinfo as "c");
&k"{</k |$i=shQ]R{ )Im<&R18 Je%vu j_Y 建立索引
rt\W=_zjc# f1~`H5r>I 最后,值得注意的是,您可以创建专门的 XML 索引来加快对 XML 列中的数据的访问。由于本文是介绍性的文章,并且示例数据量比较少,所以本文不讨论这个话题。但是,在生产环境中,定义适当的索引对于取得最佳性能是非常重要的。本文的 参考资料 小节可以帮助您了解更多关于新的 DB2 索引技术的知识。
lWd@&UoC `+R$n!A| > <H[FA TSS)qNd O1s|Wc`NR \DvyU[ Wdg ._Y< SxJqPXpn ydxk7 a= z]x!Fc/
I Wc}ZDNTM 5?
O;o;m 结束语
`&H#
v[h38 AOgP_ 本文谈到了很多基础知识,提到了 SQL/XML 的几个关键方面,并展示了如何使用 SQL/XML 查询 XML 列中的数据。当然,除了这里讨论的用法外,用 SQL 和 SQL/XML 函数还可以做更多的事。本文给出了一个 简单的 Java 例子,这个例子解释了如何使用参数标记位和 SQL/XML 来查询 XML 列中的数据。在将来的文章中我们将更详细地讨论应用程序开发。但是,接下来的文章将探索 DB2 Viper 支持的一种新的查询语言,即 XQuery 的一些有趣的方面。