 |  |
|
난이도 : 초급 Don Chamberlin, IBM Fellow, IBM Cynthia M. Saracco, Senior Software Engineer, IBM
2007 년 1 월 23 일 IBM® DB2® 9(코드명 'Viper')에는 XML 데이터의 저장, 관리, 쿼리에 대한 새로운 지원 사항이 추가되었습니다. XQuery를 사용하여 XML 칼럼에 저장된 데이터를 쿼리하는 방법을 배워봅시다.
지금까지 DB2 9의 새로운 아키텍처에 대해 들어봤을 것이고, 이것이 테이블 형태의 데이터 구조와 계층적 데이터 구조 모두를 지원한다는 것에 대해서도 들어봤을 것이다. 실제로, 이전 기술자료에서는 DB2의 새로운 XML 특징들을 정리했고, 데이터베이스 객체들을 생성하는 방법과, 이들을 XML 데이터로 채우는 방법을 설명했고, SQL과 SQL/XML을 사용하여 XML 데이터를 실행하는 방법을 설명했다. 이 글에서도 계속해서 XQuery 지원이라는 새로운 특징에 초점을 맞추며 DB2 XML 기능에 대해 살펴보고자 한다.
DB2는 XQuery를 SQL 문에 삽입 또는 래핑할 필요 없이, XQuery 식을 직접 작성할 수 있도록 한다. 더욱이, DB2의 쿼리 엔진은 기본적으로 XQuery를 처리한다. 다시 말해서, 별도로 SQL로 변환하지 않고, XQuery를 파싱, 연산, 최적화 한다는 의미이다. 물론, XQuery와 SQL 식 모두를 포함한 "Bilingual" 쿼리를 작성한다면, DB2는 그러한 쿼리 역시도 처리 및 최적화 할 것이다.
"SQL을 이용한 DB2 XML 데이터 쿼리 (한글)"에서 설명한 SQL/XML과 마찬가지로, 이 글에서는 여러 일반적인 쿼리 태스크들을 검토하고, XQuery를 사용하는 방법을 설명한다. 하지만 먼저, XQuery가 SQL과 어떻게 다른지부터 보자.
XQuery에 대하여
XQuery는 많은 부분에서 SQL과 다르다. 이 언어는 다른 특성을 가진 데이터 모델과 작동하도록 설계되었기 때문이다. XML 문서는 계층을 포함한 고유의 순서를 가지고 있다. SQL 기반의 DBMS에서 지원되는 테이블 형태의 데이터는 계층을 가지고 있지 않고(flat하고) 순서가 정해지지 않은 set 기반이다.
이러한 데이터 모델들간 차이로 인해 각각의 쿼리 언어에 많은 근본적인 차이들이 생긴다. 예를 들어, XQuery는 경로 식을 지원하여, 프로그래머들이 XML의 계층적 구조를 통해 검색할 수 있도록 하는 반면, (XML 확장이 없는) 일반 SQL은 그렇지 않다. XQuery는 Typed 및 Untyped 데이터를 지원하지만, SQL 데이터는 언제나 특정 타입으로 정의된다. XQuery는 null 값을 가지고 있지 않다. XML 문서의 경우 값을 아예 생략하기 때문이다. 반면 SQL에서는 null 값을 가질 수 있다. 또한, XQuery는 연속된 XML을 결과로 반환하고 SQL은 다양한 데이터 타입에 대응되는 결과 set을 반환한다.
이러한 것들은 XQuery와 SQL의 근본적 차이의 일부일 뿐이다. 이 글에서는 그 차이점 모두를 설명하지는 않겠지만, IBM Systems Journal paper에서 두 언어간 차이점을 보다 상세히 설명할 것이다. 지금은 XQuery 언어의 기본적인 특징을 살펴보고, 이를 사용하여 DB2 9에 있는 XML 데이터를 쿼리하는 방법을 살펴보도록 하겠다.
샘플 데이터베이스
이 글에서 사용되는 쿼리는 "DB2 Viper 시작하기 (한글)" (developerWorks, 2006년 7월)에서 만들었던 샘플 테이블로 액세스 할 것이다. Listing 1은 "items"와 "clients" 테이블 샘플을 정의한다.
Listing 1. 테이블 정의
create table items (
id int primary key not null,
brandname varchar(30),
itemname varchar(30),
sku int,
srp decimal(7,2),
comments xml
)
create table clients(
id int primary key not null,
name varchar(50),
status varchar(10),
contactinfo xml
)
|
"items.comments" 칼럼에 포함된 샘플 XML 데이터는 그림 1에, "clients.contactinfo" 칼럼에 포함된 샘플 XML은 그림 2에 나타나 있다. 연이은 쿼리 예제들은 이러한 XML 문서들에 있는 특정 엘리먼트들을 참조할 것이다.
그림 1. "items" 테이블의 "comments" 칼럼에 저장된 XML 문서 샘플
그림 2. "clients" 테이블의 "contactinfo" 칼럼에 저장된 XML 문서 샘플
쿼리 환경
이 글에 소개된 모든 쿼리들은 대화식으로(interactively) 실행되도록 설계된다. DB2 명령창(Command Line Processor)이나 DB2 제어센터(Control Center)의 DB2 명령편집기(Command Editor)를 통해 이것을 수행한다. 스크린 이미지와 명령어들은 후자에 초점을 맞추고 있다. (DB2 Viper에는 Eclipse 기반 Developer Workbench가 포함되어 있어서, 쿼리를 그래픽으로 구현할 수 있다. 이 글에서는 애플리케이션 개발 문제나 Developer Workbench에 대해서는 설명하지 않겠다.
DB2 Command Editor를 사용할 경우, Control Center를 시작하고, Tools -> Command Editor를 선택한다. 그림 3과 같은 창이 나타날 것이다. 위쪽 패인에 쿼리를 입력하고, 상단 좌측 코너에 있는 녹색 화살표를 눌러 실행하면, 아래쪽 패인 또는 별도 "Query Results" 탭에서 결과를 볼 수 있다.
그림 3. DB2 Control Center에서 시작하는 DB2 Command Editor
XQuery 예제
"SQL을 이용한 DB2 XML 데이터 쿼리 (한글)"와 마찬가지로, 이 글에서도 여러 비즈니스 시나리오들을 통해서, XQuery를 사용하여 XML 데이터 요청을 처리하는 방법을 설명한다. XQuery 안에 SQL을 십입하는 보다 복잡한 상황도 검토할 것이다.
XQuery는 원하는 방식대로 결합할 수 있는 여러 다양한 종류의 식들을 제공한다. 각 식은 다른 식에 대한 인풋으로도 사용될 수 있는 값 리스트를 리턴한다. 최상위(outermost) 식의 결과가 그 쿼리의 결과이다.
이 글에서는 중요한 XQuery 식 두 개에 초점을 맞추고자 한다. 바로 "FLWOR" 식과 경로 식(path expression)이다. FLWOR 식은 SQL의 SELECT-FROM-WHERE 식과 많은 부분 비슷하다. 아이템 리스트를 반복하고, 각 아이템에서 계산된 결과를 리턴한다. 한편, 경로 식은 XML 엘리먼트의 계층들을 검색하고, 경로의 끝에, 발견된 엘리먼트를 리턴한다.
SQL의 SELECT-FROM-WHERE 식과 마찬가지로, XQuery FLWOR 식에는 특정 키워드로 시작하는 여러 문들이 포함된다. 다음 키워드는 FLWOR 식에서 문을 시작하는데 사용된다.
for: 인풋 시퀀스를 반복하면서, 변수를 각 인풋 아이템에 바인딩 한다.
-
let: 변수를 선언하고, 여기에 값을 할당한다. 여러 아이템들을 포함하고 있는 리스트가 될 수도 있다.
-
where: 쿼리 결과의 필터링 기준을 정한다.
-
order by: 결과의 정렬 순서를 정한다.
-
return: 리턴 되는 결과를 정의한다.
XQuery의 경로 식은 일련의 "단계(step)"들로 구성되며, 슬래시(slash)로 분리된다. 가장 단순한 형태로, 각 단계는 XML 계층에서 아래쪽으로 검색하여 이전 단계에서 리턴된 엘리먼트들의 자식을 찾는다. 경로 식의 각 단계 역시 그 단계에서 리턴된 엘리먼트들을 필터링 하는 술어(predicate)를 포함하고 있고, 일정 조건에 맞는 엘리먼트만 보유한다. 예를 들어, 변수 $clients가 <Client> 엘리먼트들을 포함하고 있는 XML 문서 리스트에 속해있다면, 네 단계 경로 식 $clients/Client/Address[state = "CA"]/zip은 주소가 California로 되어 있는 클라이언트의 Zip 코드 리스트를 리턴 할 것이다.
많은 경우, FLWOR 식 또는 경로 식을 사용하여 쿼리를 작성할 수도 있다.
상위 쿼리 언어로서 DB2 XQuery 사용하기
DB2 Viper에서 직접 XQuery를 실행하려면(SQL 문에 삽입하는 것과는 반대 개념), 쿼리에 xquery라는 키워드를 달아야 한다. 이것은 DB2가 XQuery 파서를 호출하여 요청을 처리하도록 명령하는 것이다. 최상위(또는 Top-level) 언어로서 XQuery를 사용하는 경우에만 이렇게 한다. XQuery 식을 SQL에 삽입한다면, xquery 키워드를 붙일 필요가 없다. 하지만, 이 글에서는 XQuery를 주 언어로 사용할 것이기 때문에, 모든 쿼리에 xquery를 붙인다.
상위 언어로서 실행할 때, XQuery에는 인풋 데이터의 소스가 있어야 한다. XQuery가 인풋 데이터를 얻는 한 가지 방법은, DB2 테이블에서 XML 칼럼의 테이블 이름과 칼럼 이름을 구분하는 매개변수와 함께 db2-fn:xmlcolumn이라는 이름의 함수를 호출하는 것이다. db2-fn:xmlcolumn 함수는 해당 칼럼에 저장된 XML 문서 시퀀스를 리턴한다. 예를 들어, 다음 쿼리는 고객의 연락처 정보를 포함하고 있는 XML 문서 시퀀스를 리턴한다.
Listing 2. 고객의 연락처 데이터를 리턴하는 XQuery
xquery db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')
|
데이터베이스 스키마 ("데이터베이스 샘플" 섹션 참조)를 기억해 보면, "clients" 테이블의 "contactinfo" 칼럼에 이 같은 XML 문서를 저장했다. 이 칼럼과 테이블 이름들은 대문자로 지정된다. 테이블과 칼럼 이름들은 DB2의 내부 카탈로그로 작성되기 전에 대문자가 된다. XQuery는 대/소문자를 가리기 때문에, 소문자 테이블과 칼럼 이름은 DB2 카탈로그의 대문자 이름과 매치되지 못한다.
특정 XML 엘리먼트 검색하기
기본적인 태스크부터 시작하자. 여러분이 갖고 있는 모든 고객들의 팩스 번호를 검색한다고 해보자. Listing 3은 쿼리 작성하는 한 가지 방법이다.
Listing 3. 클라이언트의 팩스 데이터를 검색하는 FLWOR 식
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/fax
return $y
|
첫 번째 라인은 DB2에게 XQuery 파서를 호출할 것을 명령하고 있다. 다음 라인은 CLIENTS.CONTACTINFO 칼럼에 포함된 Client 엘리먼트들의 fax 하위 엘리먼트를 반복하도록 명령한다. 각각의 fax 엘리먼트는 변수 $y로 바인딩 된다. 세 번째 라인은, 매번 반복할 때 마다, $y 값이 리턴되도록 명령하고 있다. 결과는 Listing 4와 같은 XML 엘리먼트 시퀀스이다.
Listing 4. 이전 쿼리의 결과
<fax>4081112222</fax>
<fax>5559998888</fax>
|
이 결과에는 이 글의 관심에서 벗어난 일부 정보도 포함되어 있다. <?xml version="1.0" encoding="windows-1252" ?> 같은 XML 버전과 인코딩 데이터와 <fax xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 같은 XML 네임스페이스 정보가 그렇다. 여러분의 이해를 돕고자, 여기에서 그 정보는 생략했다. 하지만, 이것은 많은 XML 애플리케이션에는 중요할 수 있다. DB2 명령행 프로세서를 사용하여 쿼리를 실행한다면, -d 옵션을 사용하여 XML 선언 정보를 제한하고, -i 옵션을 사용하여 결과를 프린트 한다.
Listing 3의 쿼리는 Listing 5와 같은 3단계 경로식처럼 보다 자세하게 풀이될 수 있다.
Listing 5. 클라이언트 팩스 데이터를 검색하는 경로 식
xquery
db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/fax
|
경로 식의 첫 번째 단계는 db2-fn:xmlcolumn 함수를 호출하여 CLIENTS 테이블의 CONTACTINFO 칼럼에서 XML 문서 리스트를 가져오는 것이다. 두 번째 단계는, 이러한 문서들에서 모든 CLIENT 엘리먼트들을 리턴하고, 세 번째 단계는 CLIENT 엘리먼트 안에 중첩된 fax 엘리먼트를 리턴하는 것이다.
쿼리를 통해 XML로 된 결과 보다는, XML 엘리먼트 값들을 나타내는 텍스트 표현을 원한다면, return 문에 text() 함수를 호출할 수도 있다. (Listing 6)
Listing 6. Client fax 데이터의 텍스트를 가져오는 두 개의 쿼리
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/fax
return $y/text()
(or)
xquery
db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/fax/text()
|
이 쿼리의 결과는 Listing 7과 비슷할 것이다.
Listing 7. 이전 쿼리의 결과
쿼리 결과는 비교적 단순하다. 팩스 엘리먼트가 기본 데이터 유형에 기반하고 있기 때문이다. 물론, 엘리먼트들은 복잡한 유형에 기반하고 있다. 다시 말해서, 하위 엘리먼트들(또는 중첩 계층)을 포함하고 있을 수도 있다는 의미다. 클라이언트 연락처 정보의 Address 엘리먼트가 한 예이다. "DB2 Viper 시작하기 (한글)" (developerWorks, 2006년 7월)에 정의된 스키마에 따라, 여기에는 street 주소, 아파트 번호, 도시, 주, zip 코드가 포함된다. Listing 8의 XQuery가 무엇을 리턴할지를 생각해 보자.
Listing 8. 복잡한 XML 유형을 가져오는 FLWOR 식
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/Address
return $y
|
Address 엘리먼트와 이것의 모든 하위 엘리먼트들을 포함하고 있는 XML 시퀀스를 생각했다면, 여러분 생각이 맞다. Listing 9를 보자.
Listing 9. 이전 쿼리의 결과
<Address>
<street>5401 Julio Ave.</street>
<city>San Jose</city>
<state>CA</state>
<zip>95116</zip>
</Address>
. . .
<Address>
<street>1204 Meridian Ave.</street>
<apt>4A</apt>
<city>San Jose</city>
<state>CA</state>
<zip>95124</zip>
</Address>
|
주: 이 결과 샘플은 읽기 쉽도록 포맷되었다. DB2 Command Editor는 각각의 고객 주소 기록을 한 줄로 나타낸다.
XML 엘리먼트 값으로 필터링 하기
이전 XQuery 예제들을 보다 선택적인 것으로 조정할 수 있다. 예를 들어, zip 코드 95116에 살고 있는 모든 고객들의 메일링 주소를 리턴하는 방법을 생각해 보자.
여러분도 예상 했겠지만, XQuery where 문으로 XML 문서에 있는 Zip 엘리먼트의 값에 기반하여 결과를 필터링 할 수 있다. Listing 10은 Listing 8의 FLWOR 식에 where 문을 추가하여, 여러분이 원하는 주소만 가져오는 방법을 설명하고 있다.
Listing 10. 새로운 "where" 문이 있는 FLWOR 식
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/Address
where $y/zip="95116"
return $y
|
추가된 where 문은 이해하기가 쉽다. for 문은 변수 $y를 각 주소에 바인딩 한다. where 문에는 각각의 주소부터 이것의 중첩된 Zip 엘리먼트까지 검색하는 작은 경로 식이 포함된다. 이 Zip 엘리먼트의 값이 95116과 같을 경우에만, where 문은 true가 된다. (주소가 포함된다.)
경로 식에 술어를 추가해서도 같은 결과를 얻을 수 있다. (Listing 11)
Listing 11. 추가 필터링 술어를 가진 경로 식
xquery
db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/Address[zip="95116"]
|
물론, Zip 코드 값을 필터링 하고, Street 주소와 관련 없는 엘리먼트를 리턴할 수 있다. 게다가, 여러 XML 엘리먼트 값들을 하나의 쿼리 안에 필터링 할 수 있다. 다음 쿼리는 New York City (10011)의 특정 Zip 코드 또는 San Jose의 어딘가에 살고 있는 고객의 이메일 정보를 리턴한다.
Listing 12. FLWOR 식을 사용하여 여러 XML 엘리먼트 값들을 필터링 하기
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client
where $y/Address/zip="10011" or $y/Address/city="San Jose"
return $y/email
|
$y 변수를 Address 엘리먼트가 아닌 CLIENT 엘리먼트로 바인딩 하기 위해for 문을 수정했다. 서브트리(Address)의 한 부분으로 CLIENT 엘리먼트를 필터링 할 수 있고, 서브트리(email)의 또 다른 부분을 리턴한다. where 문과 return 문의 경로 식은 변수(이 경우, $y)에 바인딩 된 엘리먼트와 대응하여 작성되어야 한다.
같은 쿼리가 경로 식을 사용하여 보다 자세하게 처리될 수 있다.
Listing 13. 경로 식을 사용하여 여러 XML 엘리먼트 값들을 필터링 하기
xquery
db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client[Address/zip="10011"
or Address/city="San Jose"]/email;
|
리턴된 결과가 SQL 프로그래머가 기대했던 것과는 두 가지 중요한 방식에서 차이가 있다.
- 이메일 주소를 주지 않았던 고객들에 대한 XML 데이터가 리턴되지 않는다. 다시 말해서, San Jose 또는 zip code 10011에 살고 있는 1000명의 고객들이 있고, 700명의 고객들이 이메일 주소를 제공했다면, 700개의 이메일 주소 리스트가 리턴된다. 이것은 앞서 언급했던 XQuery와 SQL 간 근본적인 차이에서 기인한다. XQuery는 null을 사용하지 않는다.
- 같은 XML 문서에서 어떤 이메일 주소가 왔는지를 모른다. 다시 말해서, San Jose 또는 zip code 10011에 살고 있는 700 명의 고객들이 있고, 이 고객들의 두 개의 이메일 주소를 제공했다면, 1400개의 이메일 엘리먼트가 리턴된다. 각각 두 개의 이메일 주소로 기록된 700개의 레코드를 얻지 못한다는 의미이다.
두 가지 상황 모두 어떤 경우에는 바람직하고, 어떤 경우에는 그렇지 못하다. 예를 들어, 레코드에 속한 모든 계정에 이메일 공지를 보내야 한다면 XML 포맷으로 된 고객 이메일 주소 리스트를 반복하는 것은 쉬운 일이다. 하지만, Street 주소만 제공했던 고객들을 포함하여, 모든 고객에게 공지를 이메일 보내려고 한다면, 앞서 본 XQuery는 충분하지 않다.
리턴된 결과가 빠진 정보도 일정 형태로 나타내고, 같은 고객 기록(같은 XML 문서)에서 여러 이메일 주소가 검색될 경우 이를 알릴 수 있는 쿼리를 작성 할 수 있는 여러 방법들이 있다. 한 가지 방법을 보도록 하자. 고객 당 한 개의 이메일 주소를 포함하고 있는 리스트를 가져와야 한다면, 이전 쿼리의 return 문을 수정할 수 있다.
Listing 14. 고객 당 첫 번째 이메일 엘리먼트만 가져오기
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client
where $y/Address/zip="10011" or $y/Address/city="San Jose"
return $y/email[1]
|
이 쿼리로 DB2가 각각의 해당 XML 문서(고객 연락처 레코드)에서 찾은 첫 번째 이메일 엘리먼트를 리턴하게 된다. 해당 고객의 이메일 주소를 찾지 못하면 그 고객에 대한 어떤 것도 리턴하지 않는다.
XML 결과 변형
XQuery의 장점은 XML 결과를 XML 형태에서 다른 형태로 변형할 수 있다는 점이다. 예를 들어, XQuery를 사용하여 저장된 XML 문서의 전체 또는 일부를 가져와서, 결과를 HTML로 변환하여 웹 브라우저에서 쉽게 디스플레이 되도록 할 수 있다. Listing 15의 쿼리는 클라이언트의 주소를 가져와서, Zip 코드 별로 결과를 정렬하고, 결과를 정렬되지 않은(unordered) HTML 리스트의 일부인 XML 엘리먼트로 변환한다.
Listing 15. DB2 XML 데이터 쿼리와 결과를 HTML로 리턴하기
xquery
<ul> {
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client/Address
order by $y/zip
return <li>{$y}</li>
} </ul>
|
이 쿼리는 xquery 키워드로 시작하여 DB2 파서에게 XQuery가 주 언어로 사용되고 있음을 알린다. 두 번째 라인은 unordered list (<ul>)용 HTML 마크업이 결과 안에 포함되도록 명령한다. 또한 이 쿼리에 사용된 두 세트의 두 세트의 처음에, 중괄호를 사용한다. 중괄호는 DB2에게 이것을 리터럴 스트링으로 취급하기 보다는 삽입된 식으로 처리할 것을 명령한다.
세 번째 라인은 클라이언트 주소를 반복하면서, $y 변수를 각각의 주소 엘리먼트에 바인딩 한다. 네 번째 라인에는 새로운 order by 문이 포함되어, 결과가 고객 Zip 코드에 따라 오름차순으로 return되도록 지정한다. ($y에 바인딩 된 각 주소의 Zip 서브 엘리먼트) return 문은 Address 엘리먼트가 리턴되기 전에 HTML 리스트 아이템 태그들로 둘러싸일 것을 명령한다. 그리고 마지막 라인은 쿼리를 끝마치고 HTML unordered list 태그를 끝낸다.
결과는 Listing 16과 같다.
Listing 16. 이전 쿼리의 HTML 결과
<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>
|
앞서 언급된 문제에 대해 생각해 보자. 리턴된 결과에 빠진 값이 있다는 것을 알리고, 하나의 XML 문서(한 개의 고객 기록)에 반복 엘리먼트(다중 이메일 주소)가 포함되었다는 것을 알리는 XQuery를 작성하는 방법은? 한 가지 방법은 리턴된 결과를 새로운 XML 엘리먼트로 래핑하는 것이다. (Listing 17)
Listing 17. 빠진 값을 알려주고, XQuery 결과에서 엘리먼트를 반복하기
xquery
for $y in db2-fn:xmlcolumn('CLIENTS.CONTACTINFO')/Client
where $y/Address[zip="10011"] or $y/Address[city="San Jose"]
return <emailList> {$y/email} </emailList>
|
이 쿼리를 실행하면 "emailList" 엘리먼트들의 시퀀스가 리턴되고, 해당 고객 레코드 마다 하나씩 리턴된다. 각각의 emailList 엘리먼트에는 이메일 데이터가 포함된다. DB2가 고객의 레코드에서 하나의 이메일 주소를 찾는다면, 그 엘리먼트와 값을 리턴할 것이다. 여러 이메일 주소를 찾는다면, 모든 이메일 엘리먼트와 값들 모두를 리턴할 것이다. 마지막으로, 어떤 이메일 주소도 찾지 못하면, 빈 emailList 엘리먼트를 리턴한다. 따라서, 결과는 다음과 같아진다.
Listing 18. 이전 쿼리의 결과
<emailList>
<email>love2shop@yahoo.com</email>
</emailList>
<emailList/>
<emailList>
<email>beatlesfan36@hotmail.com</email>
<email>lennonfan36@hotmail.com</email>
</emailList>
. . .
|
조건문 사용하기
XML 결과를 변형하는 XQuery의 기능은 조건문 지원을 통해 애플리케이션 코드의 복잡함을 줄일 수 있다. 간단한 예를 들어보겠다. "items" 테이블에는 제품에 대해 고객이 해놓은 코멘트가 포함된 XML 칼럼이 있다. 코멘트에 응답 요청을 했던 고객에 대해, 제품 ID, 고객 ID, 메시지를 포함하고 있는 새로운 "action" 엘리먼트를 만들어서, 이 정보를 당사자가 처리할 수 있도록 라우팅 해야 한다. 하지만, 응답을 필요로 하지 않는 코멘트도 비즈니스에는 중요하고, 이들을 무시해서는 안된다. 대신, 제품 ID와 메시지를 넣어서 "info" 엘리먼트를 만든다. 다음은 XQuery if-then-else 식을 사용하여 이와 같은 태스크를 수행하는 방법이다.
Listing 19. XQuery의 "if-then-else" 식 사용하기
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>
)
)
|
쿼리 대부분 다 아는 내용이므로, 조건문을 보도록 하자. if 문은 주어진 코멘트의 ResponseRequested 서브 엘리먼트의 값이 "Yes"인지 여부를 결정한다. 그렇다면, then 문이 계산되면서, DB2는 세 개의 서브 엘리먼트(ProductID, CustomerID, Message)를 포함한 새로운 엘리먼트("action")을 리턴한다. 그렇지 않으면, else 문이 계산되어 DB2는 제품 ID와 메시지 데이터만 포함된 "info" 엘리먼트를 리턴하게 된다.
"let" 문 사용하기
한 가지를 제외하고, FLWOR 식의 모든 부분들을 사용하는 방법을 보았다. 그 한 가지가 let 문이다. 이 문은 값(여러 아이템 리스트를 포함)을 FLWOR 식의 다른 문에 사용될 수 있는 변수에 할당한다.
각 제품에 대해 받은 코멘트(comment) 리스트를 만든다고 해보자. 다음과 같은 쿼리를 사용한다.
Listing 20. "let" 문 사용하기
xquery
for $p in distinct-values
(db2-fn:xmlcolumn('ITEMS.COMMENTS')/Comments/Comment/ProductID)
let $pc := db2-fn:xmlcolumn('ITEMS.COMMENTS')
/Comments/Comment[ProductID = $p]
return
<product>
<id> { $p } </id>
<comments> { count($pc) } </comments>
</product>
|
for 문의 distinct-values 함수는 ITEMS 테이블의 COMMENTS 칼럼에 있는 코멘트 안에서 발견된 ProductID의 모든 값 리스트를 리턴한다. for 문은 변수 $p를 이러한 ProductID 값에 할당한다. $p의 각 값들에 대해, let 문은 ITEMS 칼럼을 다시 스캔하고 $pc 변수를 ProductID가 $p에 있는 ProductID와 매치하는 모든 코멘트를 포함하고 있는 리스트에 바인딩 한다. return 문은 각각의 ProductID 값에 대해 새로운 "product" 엘리먼트를 만든다. 이러한 각각의 "product" 엘리먼트들에는 두 개의 서브 엘리먼트가 포함된다. ProductID 값을 포함하고 있는 "id" 엘리먼트와 주어진 제품에 대해 얼마나 많은 코멘트를 받았는지에 대한 카운트를 포함하고 있는 "comments" 엘리먼트이다.
이 쿼리의 결과는 다음과 같다.
Listing 21. 이전 쿼리의 결과
<product>
<id>3926</id>
<comments>28</comments>
</product>
<product>
<id>4097</id>
<comments>13</comments>
</product>
|
임베디드 SQL과 XQuery
지금까지, XML 문서 조각들을 가져와서, 새로운 형태의 XML 아웃풋을 만들고, 쿼리에 지정된 조건에 따라 다른 결과를 리턴하는 XQuery 작성법을 배웠다. 간단히 말해, XQuery를 사용하여 DB2에 저장된 XML 데이터를 쿼리하는 몇 가지 방법을 배운 것이다.
이 글에서 설명한 것 이상으로 XQuery에 대해 배울 것이 좀 더 있다. 하지만, 이 글에서 설명하지 않은 내용들을 무시해서는 안된다. 바로 XQuery에 SQL을 삽입하는 방법도 그 중 하나이다. 이것은 XML과 비 XML 칼럼 값에 따라서 데이터를 필터링 하는 쿼리를 작성할 때 유용하다.
"SQL을 이용한 DB2 XML 데이터 쿼리 (한글)" 에서는 SQL 문에 XQuery 식을 삽입하여 이 태스크를 수행하는 방법을 설명했다. 여기에서는, 그 반대의 경우를 설명하겠다. SQL을 XQuery에 삽입하여 전통적인 SQL 데이터 값과 특정 XML 엘리먼트 값에 따라 결과를 제한하는 것이다.
테이블의 한 칼럼에 모든 XML 데이터를 리턴하는 db2-fn:xmlcolumn 함수 대신, db2-fn:sqlquery 함수를 호출할 수 있다. 이것은 SQL 쿼리를 실행하여 선택된 데이터만 리턴한다. db2-fn:sqlquery로 전달된 SQL 쿼리는 XML 데이터를 리턴해야만 한다. 이 XML 데이터는 나중에 XQuery에 의해서 처리될 수 있다.
Listing 22의 쿼리는, 고객이 요청한, $100 이상 되는 소비자 가격에 해당하는 제품을 포함하고 있는 코멘트 정보를 가져온다. 프라이싱 데이터는 SQL 십진수 칼럼에 저장되지만, 고객 코멘트는 XML로 저장된다. 제품 ID, 고객 ID, 고객 메시지가 포함된, 리턴된 데이터는 데이터베이스에 저장된 각 해당 코멘트에 대해, 하나의 XML "action" 엘리먼트에 포함된다.
Listing 22. XQuery에 SQL 삽입하기
xquery
for $y in
db2-fn:sqlquery('select comments from items where srp > 100')/Comments/Comment
where $y/ResponseRequested="Yes"
return (
<action>
{$y/ProductID,
$y/CustomerID,
$y/Message}
</action>
)
|
쿼리 대부분이 익숙하기 때문에, 새로운 함수 db2-fn:sqlquery.에 대해 살펴보자. DB2는 이 함수에 제공된 SQL SELECT 문을 처리하여 어떤 행에 $100가 넘는 가격의 아이템에 대한 정보가 있는지를 결정한다. 이 행에 저장된 문서들은 모든 중첩된 Comment 엘리먼트를 리턴하는 경로 식에 대한 인풋으로서 작동한다. 이 쿼리의 나머지 부분들은 XQuery where 문을 사용하여 리턴된 데이터를 필터링 하고, 선택된 코멘트의 부분들을 새로운 XML 조각들로 변형한다.
약간 다른 문제를 해결하는 방법을 생각해 보자. San Jose에 살고 있는 "Gold" 클라이언트의 모든 이메일 주소 리스트가 필요할 경우를 가정해 보자. 게다가, 한 명의 클라이언트 마다 여러 이메일 주소들이 있고, 이 모든 것이 싱글 클라이언트 레코드의 일부로 아웃풋에 포함시켜야 한다. 마지막으로, 해당 "Gold" 클라이언트가 이메일 주소를 제공하지 않았다면, 메일링 주소를 가져와야 한다. Listing 23은 이 같은 쿼리를 작성하는 방법이다.
Listing 23. 조건 문을 포함하고 있는 XQuery 안에 SQL 삽입하기
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
)
|
두 가지 측면을 짚어보도록 하자. 우선, 두 번째 라인에 삽입된 SELECT 문에는 "status" 칼럼에 기반하여 쿼리 술어를 포함하고 있고, VARCHAR 칼럼의 값을 "Gold" 스트링과 비교한다. SQL에서, 그와 같은 스트링에는 싱글 쿼트를 붙인다. 예제에서는 더블 쿼트를 사용한 것처럼 보이지만, 실제로는 대상 값("Gold") 앞뒤로 두 개의 싱글 쿼트를 사용한 것이다. "여분의" 싱글 쿼트는 이스케이프 문자이다. 스트링 기반 쿼리 술어에 두 개의 싱글 쿼트 대신 더블 쿼트를 사용하면, 신택스 에러가 생긴다.
게다가, 이 쿼리의 return 문에는 이메일 엘리먼트가 해당 고객의 레코드에 있는지의 여부를 결정하는 조건 문이 포함된다. 있다면, 이 쿼리는 모든 고객의 이메일 주소를 포함하고 있는 새로운 "emailList" 엘리먼트를 리턴한다. (해당 고객의 모든 이메일 엘리먼트) 만약 없다면, 고객의 메일링 주소를 리턴한다. (해당 고객의 Address 엘리먼트)
인덱싱
마지막으로, 특별한 XML 인덱스를 만들어서 XML 칼럼에 저장된 데이터로의 액세스 속도를 높일 수 있다. 하지만 본 서에서는 여기까지만 설명하도록 하겠다. 실행 환경에서는, 알맞은 인덱스를 정의하는 것이 최적의 성능을 얻는데 매우 중요하다. DB2의 새로운 인덱싱 기술은 참고자료 섹션을 참고하라.
요약
XQuery는 상당 부분 SQL과 다르고, 이 글에서는 바로 그러한 부분을 설명했다. 이 언어를 배워두면 작업에도 도움이 되고, XQuery와 SQL을 함께 사용하기에 적절한 시기도 분별할 수 있다. 다음 글에서는, 또 다른 주제인 DB2 XML 기능을 활용하는 자바 애플리케이션 개발 방법에 대해 설명하겠다. 본 서에 포함된 자바 예제를 통해, 자바 애플리케이션이 XQuery를 삽입하는 방법을 설명했다.
감사의 말
이 글에 도움을 주신 George Lapis, Matthias Nicola, Gary Robinson에게 감사의 말을 전한다.
기사의 원문보기
참고자료 교육
-
DB2 Viper Web site
-
"What's new in DB2 Viper: XML to the Core" (developerWorks, February 2006)
-
"DB2 Viper 시작하기 (한글)" (developerWorks, 2006년 7월): XML 데이터의 저장, 관리, 검색에 대한 새로운 기능을 제공합니다.
-
"SQL을 이용한 DB2 XML 데이터 쿼리 (한글)" (developerWorks, 2006년 12월): SQL과 SQL/XML을 사용하여 XML 칼럼에 저장된 데이터를 쿼리하는 방법을 배워봅니다.
-
XQuery from the Experts, (Howard Katz, et. al., Addison-Wesley, 2003) book excerpt
-
"Integration of SQL and XQuery in DB2" (forthcoming IBM Systems Journal issue)
-
"Firing up the Hybrid Engine" (DB2 Magazine, Quarter 3, 2005)
-
System RX: One Part Relational, One Part XML (SIGMOD 2005 Conference)
-
"Native XML Support in DB2 Universal Database" (VLDB conference, 2005)
-
"Managing XML for Maximum Return" (IBM, November 2005)
-
developerWorks Information Management zone: IBM Information Management 소프트웨어의 기술자료
-
developerWorks
technical events and webcasts.
-
DB2 Express-C, 무료 버전 DB2 Express Edition 커뮤니티.
제품 및 기술 얻기
토론
필자소개  | |  | Don Chamberlin은 Almaden Research Center 소속이며, W3C XML Query Working Group의 IBM 대표이다. 또한, XQuery 디자인이 기초를 설명한 Quilt 언어 제안의 공동 작성자이다. SQL 데이터베이스 언어의 공동 창안자로, 또한 DB2 데이터베이스 시스템과 관련한 책의 저자로서도 알려져 있다. Harvey Mudd College에서 학사 학위를 받고, Stanford University에서 박사 학위를 받았다. ACM Fellow이며, National Academy of Engineering 회원이다. |
 | |  | C. M. Saracco는 IBM Silicon Valley Laboratory의 DB2 XML 분야에서 일하고 있다. 데이터베이스 관리, XML, 웹 애플리케이션 개발을 수행하고 있다. |
기사에 대한 평가
|  |