Oracle は XMLType という組み込みデータ型で XML データをネイティブに扱えます。外部システムから受け取った XML を DB に格納して XQuery でクエリしたり、既存テーブルのデータを XML 形式に変換して API レスポンスとして使ったりする場面で活躍します。
Oracle 12c 以降は JSON のサポートも強化されましたが、レガシーシステムの XML データ連携・EDI・SOAP Web サービスとの連携では XMLType が今も広く使われています。
この記事でわかること
- XMLType 列を使って XML データをテーブルに格納する方法
- XMLQuery / XMLExists で XQuery を使って XML を検索・抽出する方法
- XMLTABLE で XML をリレーショナルデータ(行と列)に変換する方法
- XMLElement / XMLForest / XMLAgg でテーブルデータを XML に変換する方法
- 旧 API(EXTRACT / EXTRACTVALUE)と新 API(XMLQuery)の使い分け
- XMLIndex で XML 検索を高速化する方法
XMLType 列でテーブルに XML を格納する
XMLType 列を持つテーブルを作成して XML を挿入する
-- XMLType 列を持つテーブルを作成する
CREATE TABLE purchase_orders (
order_id NUMBER PRIMARY KEY,
order_date DATE,
xml_data XMLTYPE -- XML データを格納する列
);
-- XML をリテラルとして INSERT する(XMLType() コンストラクタを使う)
INSERT INTO purchase_orders(order_id, order_date, xml_data)
VALUES(
1,
SYSDATE,
XMLType('<?xml version="1.0"?>
<PurchaseOrder>
<PoNumber>PO-20240401</PoNumber>
<Vendor>ABC Corporation</Vendor>
<Items>
<Item>
<ProductId>P001</ProductId>
<Quantity>10</Quantity>
<UnitPrice>500.00</UnitPrice>
</Item>
<Item>
<ProductId>P002</ProductId>
<Quantity>5</Quantity>
<UnitPrice>1200.00</UnitPrice>
</Item>
</Items>
<TotalAmount>11000.00</TotalAmount>
</PurchaseOrder>')
);
-- CLOB から XMLType に変換して挿入する
DECLARE
v_xml_clob CLOB := '<Order><Id>2</Id><Status>PENDING</Status></Order>';
BEGIN
INSERT INTO purchase_orders(order_id, order_date, xml_data)
VALUES(2, SYSDATE, XMLType(v_xml_clob));
COMMIT;
END;
/
-- XMLType を文字列(CLOB)に変換して取得する
SELECT order_id,
xml_data.getClobVal() AS xml_text -- XMLType → CLOB
FROM purchase_orders
WHERE order_id = 1;
XMLQuery で XQuery を使って XML を抽出・検索する
XMLQuery は XQuery 言語を使って XMLType の特定要素を抽出します。Oracle 10g 以降で推奨される XML 検索の標準的な方法です。
XMLQuery で特定要素を取得する
-- XMLQuery: XQuery 式で要素を抽出する
-- RETURNING CONTENT: XMLType として返す
-- PASSING: バインドする XMLType 列を指定する
SELECT
order_id,
XMLQuery(
'$x/PurchaseOrder/PoNumber/text()'
PASSING xml_data AS "x"
RETURNING CONTENT
).getStringVal() AS po_number,
XMLQuery(
'$x/PurchaseOrder/TotalAmount/text()'
PASSING xml_data AS "x"
RETURNING CONTENT
).getNumberVal() AS total_amount
FROM purchase_orders
WHERE order_id = 1;
-- 出力: PO-20240401, 11000
-- XMLExists: 要素が存在するかどうかを検索条件にする
SELECT order_id
FROM purchase_orders
WHERE XMLExists(
'$x/PurchaseOrder/Items/Item[ProductId="P001"]'
PASSING xml_data AS "x"
);
-- P001 を含む注文を検索する
-- 複数の Item 要素を取得する(XMLTABLE と組み合わせる方が実用的)
SELECT
order_id,
XMLQuery(
'$x/PurchaseOrder/Items'
PASSING xml_data AS "x"
RETURNING CONTENT
).getClobVal() AS items_xml
FROM purchase_orders
WHERE order_id = 1;
XMLTABLE で XML をリレーショナルデータに変換する
XMLTABLE は XML の繰り返し要素(例: <Item> タグが複数)をリレーショナルテーブルの行に変換します。XML 内の配列データを普通の SELECT で扱えるようになります。
XMLTABLE で XML の繰り返し要素を行に変換する
-- XMLTABLE: XML の Items/Item を1行ずつリレーショナルデータに変換する
SELECT
p.order_id,
x.product_id,
x.quantity,
x.unit_price,
x.quantity * x.unit_price AS line_total
FROM
purchase_orders p,
XMLTABLE(
'$doc/PurchaseOrder/Items/Item' -- XQuery: 繰り返す要素のパス
PASSING p.xml_data AS "doc"
COLUMNS
product_id VARCHAR2(20) PATH 'ProductId', -- PATH で各列の XPath を指定
quantity NUMBER PATH 'Quantity',
unit_price NUMBER PATH 'UnitPrice'
) x
WHERE p.order_id = 1;
-- 出力例:
-- ORDER_ID PRODUCT_ID QUANTITY UNIT_PRICE LINE_TOTAL
-- 1 P001 10 500.00 5000.00
-- 1 P002 5 1200.00 6000.00
-- XMLTABLE と GROUP BY を組み合わせた集計クエリ
SELECT
p.order_id,
COUNT(x.product_id) AS item_count,
SUM(x.quantity * x.unit_price) AS total_amount
FROM
purchase_orders p,
XMLTABLE(
'$doc/PurchaseOrder/Items/Item'
PASSING p.xml_data AS "doc"
COLUMNS
product_id VARCHAR2(20) PATH 'ProductId',
quantity NUMBER PATH 'Quantity',
unit_price NUMBER PATH 'UnitPrice'
) x
GROUP BY p.order_id;
XMLElement・XMLForest・XMLAgg でデータを XML に変換する
既存のリレーショナルテーブルのデータを XML 形式に変換するには、XMLElement(要素の生成)・XMLForest(複数要素をまとめて生成)・XMLAgg(複数行を XML に集約)を使います。
XMLElement・XMLForest・XMLAgg でデータを XML に変換する
-- XMLElement: 単一の XML 要素を生成する
SELECT
XMLElement("Employee",
XMLElement("EmployeeId", employee_id),
XMLElement("Name", first_name || ' ' || last_name),
XMLElement("Salary", salary)
).getClobVal() AS emp_xml
FROM employees
WHERE department_id = 50
FETCH FIRST 3 ROWS ONLY;
-- XMLForest: 複数の要素を一度に生成する(列名が要素名になる)
SELECT
XMLElement("Employee",
XMLForest(
employee_id AS "EmployeeId",
first_name AS "FirstName",
last_name AS "LastName",
salary AS "Salary"
)
).getClobVal() AS emp_xml
FROM employees
WHERE department_id = 50
FETCH FIRST 3 ROWS ONLY;
-- XMLAgg: 複数行の XML 要素を集約して 1 つの XML にまとめる
SELECT
XMLElement("Department",
XMLAttributes(department_id AS "id"), -- 属性の追加
XMLAgg(
XMLElement("Employee",
XMLForest(
employee_id AS "Id",
last_name AS "Name",
salary AS "Salary"
)
)
ORDER BY last_name -- ORDER BY で要素の順序を指定
)
).getClobVal() AS dept_xml
FROM employees
GROUP BY department_id
HAVING department_id = 50;
-- 出力例(一部):
-- <Department id="50">
-- <Employee><Id>100</Id><Name>King</Name><Salary>24000</Salary></Employee>
-- <Employee><Id>101</Id><Name>Kochhar</Name><Salary>17000</Salary></Employee>
-- </Department>
旧 API(EXTRACT / EXTRACTVALUE)と新 API の使い分け
EXTRACT / EXTRACTVALUE は非推奨
EXTRACT(xmltype, xpath) と EXTRACTVALUE(xmltype, xpath) はXPath 1.0 ベースの旧 API です。Oracle 12c 以降では非推奨とされており、将来のバージョンで削除される可能性があります。新規開発では XMLQuery / XMLExists / XMLTABLE を使ってください。
旧 API と新 API の対応
-- 旧 API (EXTRACTVALUE): 単一テキストノードを取得する(非推奨)
SELECT
EXTRACTVALUE(xml_data, '/PurchaseOrder/PoNumber') AS po_number
FROM purchase_orders
WHERE order_id = 1;
-- 新 API (XMLQuery): 同等の処理(推奨)
SELECT
XMLQuery(
'$x/PurchaseOrder/PoNumber/text()'
PASSING xml_data AS "x"
RETURNING CONTENT
).getStringVal() AS po_number
FROM purchase_orders
WHERE order_id = 1;
-- 旧 API (EXTRACT): XMLType として取得する(非推奨)
SELECT
EXTRACT(xml_data, '/PurchaseOrder/Items') AS items_xml
FROM purchase_orders
WHERE order_id = 1;
-- 新 API (XMLQuery RETURNING CONTENT): 同等の処理(推奨)
SELECT
XMLQuery(
'$x/PurchaseOrder/Items'
PASSING xml_data AS "x"
RETURNING CONTENT
).getClobVal() AS items_xml
FROM purchase_orders
WHERE order_id = 1;
XMLIndex で XML 検索を高速化する
XMLType 列に対して XMLExists や XMLQuery でフィルタリングするクエリが遅い場合、XMLIndex を作成することでパフォーマンスを改善できます。
XMLIndex の作成と確認
-- XMLIndex を作成する(XMLExists / XMLQuery の検索を高速化する)
CREATE INDEX po_xml_idx ON purchase_orders(xml_data)
INDEXTYPE IS XDB.XMLIndex
PARAMETERS ('PATHS (/PurchaseOrder/PoNumber, /PurchaseOrder/Items/Item/ProductId)');
-- PATHS: インデックス化する XPath を指定する(省略すると全パスを対象にする)
-- XMLIndex の確認
SELECT index_name, index_type, table_name, status
FROM USER_INDEXES
WHERE table_name = 'PURCHASE_ORDERS';
-- XMLIndex が使われることを確認する(実行計画)
EXPLAIN PLAN FOR
SELECT order_id
FROM purchase_orders
WHERE XMLExists(
'$x/PurchaseOrder/Items/Item[ProductId="P001"]'
PASSING xml_data AS "x"
);
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'TYPICAL'));
-- XML INDEX SCAN が表示されれば XMLIndex が使われている
-- XMLIndex を削除する
DROP INDEX po_xml_idx;
まとめ
- XMLType:Oracle 組み込みの XML データ型。XMLType() コンストラクタで文字列・CLOB から変換する。getClobVal() / getStringVal() / getNumberVal() で値を取り出す
- XMLQuery:XQuery 式で XML 要素を抽出する。PASSING でバインド変数を指定。RETURNING CONTENT で結果を XMLType として受け取る
- XMLExists:XQuery 条件で XML を検索する WHERE 句フィルタ。XMLIndex と組み合わせると高速
- XMLTABLE:繰り返し要素(配列的な構造)をリレーショナル行に変換する。COLUMNS でXPathと型を指定
- XMLElement / XMLForest / XMLAgg:リレーショナルデータを XML に変換する。XMLAttributes で属性を追加できる
- EXTRACT / EXTRACTVALUE は非推奨:Oracle 12c 以降は XMLQuery / XMLTABLE を使う
- XMLIndex:XMLExists / XMLQuery の検索対象パスを指定してインデックス化する
Oracle の JSON 処理(JSON_VALUE / JSON_TABLE)との比較は Oracle JSON完全ガイドを参照してください。UTL_HTTP で外部 XML API を呼び出してパースする方法は UTL_HTTP完全ガイドも参照してください。