Hãy khởi đầu nhanh chóng với DB2 9 pureXML
Phần 4: Truy vấn dữ liệu DB2 XML bằng XQuery
Giới thiệu về XQuery
XQuery khác với SQL ở một số khía cạnh chính, phần lớn là do các ngôn ngữ được thiết kế để
làm việc với các mô hình dữ liệu khác nhau có các đặc điểm khác nhau. Các tài liệu XML chứa
các hệ thống phân cấp và sở hữu một trật tự vốn có. Các cấu trúc dữ liệu dạng bảng được các
DBMS dựa trên SQL hỗ trợ là phẳng và dựa vào thiết lập. Như vậy, các hàng không có thứ tự.
Những khác biệt giữa các mô hình dữ liệu này sinh ra một số các khác biệt cơ bản trong các
ngôn ngữ truy vấn riêng của chúng, bao gồm:
XQuery hỗ trợ các biểu thức đường dẫn để cho phép các lập trình viên chuyển hướng
thông qua cấu trúc phân cấp của XML, trong khi SQL thuần (không có các phần mở rộng
XML) thì không.
XQuery hỗ trợ cả hai dữ liệu được định kiểu và không được định kiểu, trong khi dữ liệu
SQL luôn luôn được định nghĩa với một kiểu cụ thể.
XQuery thiếu các giá trị bằng không (null) vì các tài liệu XML bỏ qua dữ liệu còn thiếu
hoặc chưa rõ. Tất nhiên, SQL sử dụng các giá trị bằng không để biểu diễn các giá trị dữ
liệu còn thiếu hoặc chưa rõ.
XQuery trả về các chuỗi dữ liệu XML, trong khi SQL trả về các tập kết quả của các kiểu
dữ liệu SQL khác nhau.
Đây là một tập hợp con về những khác biệt cơ bản giữa XQuery và SQL. Việc cung cấp một
danh sách đầy đủ nằm ngoài phạm vi của bài viết giới thiệu này, nhưng một bài viết của Tạp chí
Các hệ thống IBM đã bàn luận về các khác biệt ngôn ngữ cụ thể hơn. Bài viết này tập trung vào
một số khía cạnh cơ bản của ngôn ngữ XQuery và cách bạn có thể sử dụng nó để truy vấn dữ liệu
XML trong DB2 V9.
Về đầu trang
Cơ sở dữ liệu mẫu
Các truy vấn trong bài viết này truy cập các bảng mẫu được tạo ra trong Phần 1 của loạt bài này.
Để xem lại nhanh, Liệt kê 1 định nghĩa các bảng items (các mặt hàng) và clients (các khách
hàng) mẫu.
Liệt kê 1. Các định nghĩa bảng
<CommentID>514</CommentID>
<ProductID>3926</ProductID>
<CustomerID>3227</CustomerID>
<Message>Where can I find a supplier in San Jose?</Message>
<ResponseRequested>Yes</ResponseRequested>
</Comment>
</Comments>
Liệt kê 3. Tài liệu mẫu XML được lưu trữ trong cột Contactinfo của bảng Clients
<Client>
<Address>
<street>5401 Julio Ave.</street>
<city>San Jose</city>
<state>CA</state>
<zip>95116</zip>
</Address>
<phone>
<work>4084630000</work>
<home>4081111111</home>
<cell>4082222222</cell>
</phone>
<fax>4087776666</fax>
<email>[email protected]</email>
</Client>
Về đầu trang
Môi trường truy vấn
Thiết kế tất cả các truy vấn trong bài viết này được ban hành tương tác với nhau. Bạn có thể làm
for: Lặp lại qua một chuỗi đầu vào, kết hợp một biến với lần lượt từng mặt hàng đầu
vào.
let: Khai báo một biến và gán cho nó một giá trị, có thể là một danh sách chứa nhiều
mặt hàng.
where: Quy định các tiêu chuẩn để lọc các kết quả truy vấn.
order by: Quy định thứ tự sắp xếp của kết quả.
return: Định nghĩa kết quả được trả về.
Một biểu thức đường dẫn trong XQuery gồm có một loạt các bước, được phân cách bằng các ký
tự gạch chéo. Theo dạng đơn giản nhất, mỗi bước chuyển hướng đi xuống trong một hệ thống
phân cấp XML để tìm phần tử con của các phần tử được bước trước đó trả về. Mỗi bước trong
một biểu thức đường dẫn cũng có thể chứa một biến vị ngữ lọc các phần tử được bước đó trả về,
chỉ giữ lại các phần tử đáp ứng một số điều kiện. Ví dụ, giả sử rằng biến $clients liên kết với
một danh sách các tài liệu XML có chứa các phần tử <Client>, biểu thức đường dẫn bốn bước
$clients/Client/Address[state = "CA"]/zip sẽ trả về danh sách các mã vùng bưu điện
cho các khách hàng có địa chỉ ở California.
Trong nhiều trường hợp, có thể viết một câu truy vấn bằng cách sử dụng hoặc một biểu thức
FLWOR hoặc một biểu thức một đường dẫn.
Sử dụng XQuery của DB2 như một ngôn ngữ truy vấn cao cấp
Để thực hiện trực tiếp một XQuery trong DB2 V9 (trái với việc nhúng nó vào một câu lệnh
SQL), bạn phải bắt đầu truy vấn bằng từ khóa xquery. Từ khóa này ra lệnh cho DB2 gọi trình
phân tích cú pháp XQuery của nó để xử lý yêu cầu của bạn. Lưu ý rằng bạn chỉ cần làm điều này
khi bạn đang sử dụng XQuery như ngôn ngữ ngoài cùng (hoặc mức cao nhất). Nếu bạn đã nhúng
biểu thức XQuery trong SQL, bạn không cần bắt đầu với từ khóa xquery. Tuy nhiên, bài viết
này sử dụng XQuery như là ngôn ngữ chính, vì vậy tất cả các truy vấn đều được bắt đầu bằng
xquery.
Khi chạy như một ngôn ngữ mức cao, XQuery cần phải có một nguồn dữ liệu đầu vào. Một cách
để một XQuery có thể nhận được dữ liệu đầu vào là gọi một hàm tên là db2-fn:xmlcolumn với
một tham số, nhận biết tên bảng và tên cột của một cột XML trong một bảng DB2. Hàm db2-
fn:xmlcolumn trả về chuỗi các tài liệu XML, được lưu trữ trong cột cụ thể. Ví dụ, truy vấn sau
đây trong Liệt kê 4 trả về một chuỗi các tài liệu XML có chứa thông tin liên hệ của khách hàng.
Một nhận xét ngẫu nhiên là, kết quả đầu ra cũng sẽ có một số thông tin không phải là mối quan
tâm lớn trong bài viết này: phiên bản và dữ liệu mã hóa XML, chẳng hạn như <?xml
version="1.0" encoding="windows-1252" ?>, và thông tin về không gian tên XML, chẳng
hạn như <fax xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">. Để giúp bạn
dễ dàng làm theo kết quả đầu ra hơn, thông tin đó được bỏ ra khỏi bài viết này. Tuy nhiên, nó có
thể lại quan trọng đối với một số ứng dụng XML. Nếu bạn sử dụng bộ xử lý dòng lệnh DB2 để
chạy các truy vấn của mình, bạn có thể sử dụng tùy chọn -d để chặn thông tin khai báo XML và
chọn tùy chọn -i để in các kết quả theo cách dễ nhìn.
Truy vấn được hiển thị trong Liệt kê 5 có thể được biểu diễn có phần chính xác như một biểu
thức đường dẫn ba bước, như trong Liệt kê 7.
Liệt kê 7. Biểu thức đường dẫn để lấy ra dữ liệu fax của khách hàng
xquery
db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/fax
Bước đầu tiên của biểu thức đường dẫn gọi hàm db2-fn:xmlcolumn để nhận được một danh
sách các tài liệu XML trong cột CONTACTINFO của bảng CLIENTS. Bước thứ hai trả về tất cả
các phần tử Client trong các tài liệu này và bước thứ ba trả về các phần tử fax được lồng bên
trong các phần tử Client này.
Nếu bạn không quan tâm đến việc nhận được các đoạn XML từ truy vấn của bạn nhưng thay vì
chỉ muốn một biểu diễn văn bản của các giá trị phần tử XML có đủ điều kiện, bạn có thể gọi hàm
text() trong mệnh đề return của bạn, như trong Liệt kê 8.
Liệt kê 8. Hai truy vấn để lấy ra biểu diễn văn bản của dữ liệu fax của khách hàng
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/fax
return $y/text()
(hay)
</Address>
. . .
<Address>
<street>1204 Meridian Ave.</street>
<apt>4A</apt>
<city>San Jose</city>
<state>CA</state>
<zip>95124</zip>
</Address>
Lưu ý: Kết quả đầu ra mẫu được định dạng để làm cho bạn dễ đọc hơn. DB2 Command Editor
sẽ hiển thị từng bản ghi địa chỉ của khách hàng trên một dòng.
Lọc các giá trị phần tử XML
Bạn có thể cải thiện các ví dụ XQuery trước đây để có tính chọn lọc hơn nữa. Ví dụ, hãy xem xét
cách trả về các địa chỉ gửi thư của tất cả các khách hàng đang sống trong vùng có mã vùng bưu
điện của Mỹ là 95116.
Như bạn có thể tưởng tượng, mệnh đề where của XQuery cho phép bạn lọc các kết quả dựa trên
giá trị của phần tử zip (mã vùng bưu điện) trong các tài liệu XML của bạn. Liệt kê 12 cho thấy
cách thêm một mệnh đề where vào biểu thức FLWOR trước đó trong Liệt kê 10 để nhận được
chỉ các địa chỉ mà bạn quan tâm.
Liệt kê 12. Biểu thức FLWOR với một mệnh đề "where" mới
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/Address
where $y/zip="95116"
return $y
Mệnh đề where được bổ sung khá dễ hiểu. Mệnh đề for liên kết biến $y với lần lượt từng địa
chỉ. Mệnh đề where chứa một biểu thức đường dẫn nhỏ chuyển hướng từ mỗi địa chỉ đến phần tử
zip lồng nhau của nó. Mệnh đề where là đúng (và địa chỉ đó được giữ lại) chỉ khi giá trị của phần
db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client[Address/zip="10011"
or Address/city="San Jose"]/email;
Điều không rõ ràng khi xem xét lại cả hai dạng truy vấn này là các kết quả được trả về sẽ khác
với hai cách đáng kể so với những gì mà một lập trình viên SQL có thể mong đợi:
1. Bạn sẽ không nhận dữ liệu XML được trả về cho các khách hàng có đủ điều kiện vẫn
chưa cung cấp cho bạn các địa chỉ e-mail của họ. Nói cách khác, nếu bạn có 1000 khách
hàng sống ở San Jose hoặc trong vùng có mã vùng bưu điện là 10011 và 700 khách hàng
mỗi người cung cấp cho bạn một địa chỉ e-mail, bạn sẽ nhận được một danh sách 700 địa
chỉ e-mail được trả về. Điều này là do sự khác biệt cơ bản giữa XQuery và SQL đã nói ở
trên: XQuery không sử dụng các giá trị bằng không.
2. Bạn sẽ không biết các địa chỉ e-mail nào đã được bắt nguồn từ cùng một tài liệu XML.
Nói cách khác, nếu bạn có 700 khách hàng đang sống ở San Jose hoặc trong vùng có mã
vùng bưu điện là 10011 và mỗi người đã cung cấp cho bạn hai địa chỉ e-mail, thì bạn sẽ
nhận được một danh sách có 1400 phần tử email được trả về. Bạn sẽ không nhận được
một 700 bản ghi liên tiếp nhau, mà mỗi bản ghi có hai địa chỉ e-mail.
Cả hai tình huống có thể là mong muốn trong một số trường hợp và là không mong muốn trong
những trường hợp khác. Ví dụ, nếu bạn cần gửi e-mail thông báo đến mọi tài khoản có đủ điều
kiện mà bạn có trong bản ghi, thì lặp lại qua một danh sách các địa chỉ e-mail của khách hàng
theo định dạng XML là cách dễ dàng để làm trong một ứng dụng. Tuy nhiên, nếu bạn muốn gửi
e-mail chỉ có một thông báo đến mọi khách hàng, bao gồm cả những người đã chỉ cung cấp cho
bạn các địa chỉ đường phố của họ, thì các XQuery đã hiển thị trước đây sẽ không đủ.
Có nhiều cách để bạn có thể viết lại truy vấn này sao cho các kết quả trả về biểu thị thông tin còn
thiếu trong một số mẫu và cho biết khi nhiều địa chỉ e-mail đã được bắt nguồn từ cùng một bản
ghi của khách hàng, đó là, cùng một tài liệu XML (nói thêm về điều này sau). Tuy nhiên, nếu tất
cả mọi thứ bạn muốn làm là lấy ra một danh sách có chứa một địa chỉ e-mail cho mỗi khách hàng
có đủ điều kiện, thì bạn có thể sửa đổi mệnh đề return của truy vấn trước đó một chút.
Liệt kê 16. Chỉ lấy ra phần tử email đầu tiên cho mỗi khách hàng
xquery
Dòng thứ ba lặp lại qua các địa chỉ khách hàng, liên kết biến $y với lần lượt từng phần tử
địa chỉ.
Dòng thứ tư bao gồm một mệnh đề order by mới, quy định rằng các kết quả phải được
trả về theo thứ tự tăng dần (thứ tự mặc định) dựa trên các mã vùng bưu điện của khách
hàng (phần tử con zip của mỗi địa chỉ được liên kết với $y).
Mệnh đề return cho biết rằng các phần tử Address sẽ được bao quanh bởi các thẻ mục
danh sách HTML trước khi trả về.
Dòng cuối cùng kết thúc truy vấn và hoàn thành thẻ danh sách không theo thứ tự HTML.
Kết quả đầu ra xuất hiện tương tự như trong Liệt kê 18.
Liệt kê 18. Kết quả đầu ra HTML mẫu của truy vấn trước đó
<ul>
<li>
<Address>
<street>9407 Los Gatos Blvd.</street>
<city>Los Gatos</city>
<state>CA</state>
<zip>95032</zip>
</Address>
</li>
<li>
<Address>
<street>4209 El Camino Real</street>
<city>Mountain View</city>
<state>CA</state>
<zip>95033</zip>
</Address>
</li>
. . .
</ul>
Sử dụng logic điều kiện
Khả năng của XQuery để chuyển đổi đầu ra XML có thể được kết hợp với sự hỗ trợ có sẵn của
nó cho logic điều kiện để giảm bớt sự phức tạp của mã ứng dụng. Hãy xem xét một ví dụ đơn
giản. Bảng Items (Các mặt hàng) bao gồm một cột XML chứa các ý kiến mà các khách hàng đã
đóng góp về các sản phẩm. Đối với các khách hàng đã yêu cầu trả lời lại ý kiến của họ, bạn có
thể cần tạo các phần tử Action (Hoạt động) mới có chứa mã định danh sản phẩm, mã định danh
khách hàng và thông báo để cho bạn có thể định tuyến thông tin này đến những người thích hợp
để xử lý. Tuy nhiên, các ý kiến mà không yêu cầu câu trả lời vẫn quan trọng cho nghiệp vụ và
bạn không muốn bỏ qua chúng. Thay vào đó, hãy tạo một phần tử Info (Thông tin) với chỉ mã
định danh và thông báo sản phẩm. Đây là cách bạn có thể sử dụng một biểu thức XQuery if-
then-else để hoàn thành nhiệm vụ này:
Liệt kê 21. Sử dụng một biểu thức "if-then-else" trong một XQuery
xquery
for $y in db2-fn:xmlcolumn('ITEMS.COMMENTS')/Comments/Comment
return (
if ($y/ResponseRequested = 'Yes')
then <action>
{$y/ProductID,
$y/CustomerID,
$y/Message}
</action>
else ( <info>
{$y/ProductID,
$y/Message}
</info>
)
)
ProductID khác biệt. Mỗi một trong các phần tử sản phẩm này chứa hai phần tử con: một phần tử
id có chứa giá trị ProductID và một phần tử các ý kiến có chứa tổng số có bao nhiêu ý kiến đã
nhận được với sản phẩm cụ thể đó.
Kết quả của truy vấn ví dụ này có thể trông tương tự như Liệt kê 23.
Liệt kê 23. Kết quả đầu ra mẫu cho truy vấn trước đó
<product>
<id>3926</id>
<comments>28</comments>
</product>
<product>
<id>4097</id>
<comments>13</comments>
</product>
Về đầu trang
XQueries với SQL nhúng
Bây giờ, bạn đã thấy cách viết XQueries lấy ra các đoạn tài liệu XML, tạo các dạng kết quả đầu
ra XML mới và trả về kết quả đầu ra khác nhau dựa trên các điều kiện đã quy định trong chính
các truy vấn đó. Tóm lại, bạn đã tìm hiểu một vài cách sử dụng XQuery để truy vấn dữ liệu XML
được lưu trong DB2.
Để chắc chắn, có nhiều cách để tìm hiểu về XQuery hơn cách mà bài viết ngắn gọn này trình
bày. Nhưng có một chủ đề rộng hơn vẫn chưa được nêu ra: cách nhúng SQL trong XQuery. Làm
như vậy có thể có ích nếu bạn cần viết các truy vấn lọc dữ liệu dựa trên các giá trị cột XML và
không XML.
Bạn có thể nhớ lại trong Phần 3 của loạt bài này về cách nhúng các biểu thức XQuery đơn giản
trong một câu lệnh SQL để thực hiện nhiệm vụ này. Ở đây, hãy xem xét cách làm ngược lại:
SQL được nhúng trong XQuery để hạn chế các kết quả dựa trên cả các giá trị dữ liệu SQL truyền
thống lẫn các giá trị phần tử XML cụ thể.
Để thay thế cho hàm db2-fn:xmlcolumn, trả về tất cả dữ liệu XML trong một cột của một bảng,
Với suy nghĩ này, hãy xem cách bạn có thể giải quyết vấn đề hơi khác một chút. Hãy tưởng
tượng rằng bạn cần một danh sách tất cả các địa chỉ e-mail của các khách hàng Vàng đang sống
ở San Jose. Hơn nữa, nếu bạn có nhiều địa chỉ e-mail cho một khách hàng duy nhất, bạn muốn
tất cả chúng được bao gồm trong kết quả đầu ra như một phần của một bản ghi khách hàng duy
nhất. Cuối cùng, nếu một khách hàng Vàng có đủ điều kiện lại không cung cấp cho bạn một địa
chỉ e-mail, thì bạn cần lấy địa chỉ gửi thư của người đó. Liệt kê 25 cho thấy một cách để viết như
một truy vấn như vậy:
Liệt kê 25. Nhúng SQL trong một XQuery có logic điều kiện
xquery
for $y in
db2-fn:sqlquery('select contactinfo from clients where status=''Gold''
')/Client
where $y/Address/city="San Jose"
return (
if ($y/email) then <emailList>{$y/email}</emailList>
else $y/Address
)
Hai khía cạnh của truy vấn này đáng được giải thích. Trước tiên, câu lệnh SELECT được nhúng
trong dòng thứ hai chứa một biến vị ngữ truy vấn dựa trên cột Status (Trạng thái), so sánh giá trị
của cột VARCHAR này với chuỗi Gold. Trong SQL, người ta bao quanh chuỗi như vậy bằng các
dấu ngoặc đơn. Lưu ý rằng mặc dù các ví dụ có thể sử dụng các dấu ngoặc kép, nó thực sự sử
dụng hai dấu ngoặc đơn trước và sau giá trị so sánh (''Gold''). Các dấu ngoặc đơn thêm vào là các
ký tự thoát. Nếu bạn sử dụng các dấu ngoặc kép quanh biến vị ngữ truy vấn dựa trên chuỗi ký tự
của bạn, thay cho cặp dấu ngoặc đơn, bạn sẽ nhận được một lỗi cú pháp.
Ngoài ra, mệnh đề return trong truy vấn này chứa logic điều kiện để xác định xem một phần tử
e-mail đã có mặt trong bản ghi của một khách hàng cụ thể chưa. Nếu có, truy vấn này trả về một
phần tử emailList mới có chứa tất cả địa chỉ e-mail của khách hàng (có nghĩa là, tất cả các phần
tử e-mail cho khách hàng đó). Nếu không, nó trả về địa chỉ gửi thư của khách hàng (có nghĩa là,