ヘッダーをスキップ
Oracle® XML DB開発者ガイド
11gリリース2 (11.2)
B70200-03
  目次へ移動
目次
索引へ移動
索引

前
 
次
 

12 XMLデータの全文検索

この章では、Oracleを使用したXMLの全文検索について説明します。Oracle SQL関数containsおよびOracle XPath関数ora:containsの使用方法についても説明します。これらはOracle Databaseにより使用され、XMLデータの全文検索を実行する2つの関数です。


関連項目:

Oracle Textの詳細は、『Oracle Textリファレンス』および『Oracle Textアプリケーション開発者ガイド』を参照してください。

この章の内容は次のとおりです。

XMLの全文検索の概要

Oracleは、Oracle Databaseにより管理される文書の全文検索をサポートしています。

文書がXMLの場合は、その文書のXML構造を使用して全文検索を限定できます。たとえば、全文検索を使用してワード"electric"が含まれる発注書をすべて検索するとします。発注書がXML形式の場合は、コメント内にワード"electric"が含まれるすべての発注書を検索するか、または明細項目の下にあるコメント内にワード"electric"が含まれるすべての発注書を検索するかして、検索内容を限定できます。

XML文書がXMLType型の場合は、その文書のXML構造を使用して問合せの結果を投影できます。たとえば、コメント内にワード"electric"が含まれるすべての発注書を検索した後で、コメント全体を戻すか、またはワード"electric"が含まれるコメントのみを戻すことができます。

全文検索と他の検索タイプの比較

全文検索は、構造化検索や部分文字列検索と次の点で異なります。

  • 全文検索では、部分文字列ではなく単語全体が検索されます。文字列"law"が含まれるコメントの部分文字列検索を行うと、"my lawn is going wild"が含まれるコメントが戻されます。ワード"law"の全文検索では、このコメントは戻されません。

  • 全文検索では、部分文字列検索ではサポートできない言語ベースとワードベースの検索がいくつかサポートされています。たとえば、言語ベースの検索を使用すると、"mouse"と言語語幹が同じワードを含むすべてのコメントを検索でき、Oracle Textでは"mouse"と"mice"が検索されます。ワードベースの検索の例では、"wild"を含む5ワード以内にワード"lawn"があるすべてのコメントを検索できます。

  • 全文検索には通常、関連性の概念が含まれています。たとえば、ワード"lawn"が含まれるすべてのコメントを全文検索する場合、ある結果は他の結果より関連性が高い場合があります。関連性は通常、検索ワード(または類似ワード)の文書内での出現回数に関連しています。

XMLデータの検索

XML検索は、非構造化文書の検索とは異なります。非構造化文書の検索では通常、一連の文書を検索して、指定したテキスト述語を満たす文書を戻します。XML検索では多くの場合、XML文書の構造を使用して検索を限定します。また、検索を満たす文書の一部のみを戻す場合があります。

全文検索およびXML構造を使用した文書の検索

全文検索とXML構造が含まれる検索の実行方法には、次の2通りがあります。

  • Oracle SQL関数containsを使用して、全文述語内に構造を含める方法:

    WHERE contains(doc, 'electric INPATH (/purchaseOrder/items/item/comment)') > 0
    

    関数containsはSQLの拡張機能であり、すべての問合せで使用できます。CONTEXT全文索引が必要です。

  • ora:contains XPath関数を使用して、構造内に全文述語を含める方法:

    '/purchaseOrder/items/item/comment[ora:contains(text(), "electric")>0]'
    

    XPath関数ora:containsはXPathの拡張機能であり、SQL/XML関数XMLQueryXMLTableまたはXMLExistsのコールで使用できます。

全文検索の例について

この項では、この章の例について詳細に説明します。

ロールおよび権限

例を実行するには、データベース・ロールCTXAPPCONNECTRESOURCEが必要です。また、CTXSYSパッケージCTX_DDLに対するEXECUTE権限も必要です。

全文検索の例に使用するスキーマおよびデータ

この章の例は、W3Cの「XML Schema Part 0: Primer」にある「The Purchase Order Schema」に基づいています。

例で使用しているデータは、「発注書のXML文書、po001.xml」の文書からのものです。

この章の例で使用している表は、「CREATE TABLE文」「CREATE TABLE文」で定義されています。ただし、パフォーマンスの例の一部は、より大きい表(purchase_orders_xmltype_big)に基づいており、この表はダウンロード可能なバージョンにのみ含まれています。http://www.w3.org/TR/xmlschema-0/#po.xmlを参照してください。

一部の例は、データ型VARCHAR2を使用しています。XMLType型を使用するものもあります。VARCHAR2を使用している例はすべて、XMLTypeを使用することもできます。

CONTAINSおよびora:containsの概要

この項の内容は次のとおりです。

SQL関数CONTAINSの概要

Oracle SQL関数containsは、[schema.]columntext_queryと一致する行の正数を戻し、一致しない場合は0(ゼロ)を戻します。それ以外の場合は、0(ゼロ)を戻します。型CONTEXTの索引が必要です。検索対象の列にCONTEXT索引がない場合、containsではエラーが発生します。

CONTAINS構文

contains([schema.]column, text_query VARCHAR2 [,label NUMBER])
RETURN NUMBER

例12-1に、Oracle SQL関数containsを使用した一般的な問合せを示します。表purchase_ordersで、doc列に単語"lawn"が含まれ、idが25よりも小さい値の各行のidが戻されます。

例12-1 Oracle SQL関数CONTAINSを使用した単純な問合せ

SELECT id FROM purchase_orders WHERE contains(doc, 'lawn') > 0 AND id < 25;

docは、一連のXML文書が含まれる列であるとします。この場合、docに対して、そのXML構造を使用して問合せを限定する全文検索を実行できます。例12-2の問合せでは、表purchaseordersで、XML要素commentのtext()ノードにワード"lawn"が含まれるdoc列のid値が戻されます。

例12-2 CONTAINSおよびWITHINを使用した問合せの制限

SELECT id FROM purchase_orders WHERE contains(doc, 'lawn WITHIN comment') > 0;

INPATH演算子およびXPath式を使用すると、XML構造によるさらに複雑な限定を適用できます。例12-3の問合せでは、XPath式/purchaseOrder/items/item/commentのターゲットとなるcomment要素のtext()ノードに、ワード"electric"が含まれる発注書が検索されます。

例12-3 CONTAINSおよびINPATHを使用した問合せの制限

SELECT id FROM purchase_orders
  WHERE contains(doc, 'electric INPATH (/purchaseOrder/items/item/comment)') > 0;

XPath関数ora:containsの概要

XPath関数ora:containsは、XQuery式内のXPath式や、SQL/XML関数XMLQueryXMLTableXMLExistsのコールで使用できます。これは、全文述語を伴う構成検索を制限する場合に使用されます。標準のメカニズムを使用してXPathを拡張する、Oracle XML DB名前空間ora内のユーザー定義関数です。索引は不要ですが、索引を使用することでパフォーマンスを向上できます。

ora:contains構文

ora:contains(input_text NODE*, text_query STRING 
             [,policy_name STRING]
             [,policy_owner STRING])

関数ora:containsinput_texttext_queryと一致した場合は正の整数を戻し(数字が大きいほど、一致の関連性は高くなります)、一致しない場合はゼロを戻します。XQuery式で使用される場合、XQueryの戻り型はxs:integer()です。XQuery式の外部でXPath式で使用される場合、XPathの戻り型はnumberです。

引数input_textは単一のテキスト・ノードまたは属性に対して評価される必要があります。ora:containstext_queryの構文およびセマンティクスはcontainstext_queryと同じですが、次の制限事項があります。

  • 引数text_queryに構造演算子(WITHININPATHまたはHASPATH)を含めることはできません。

  • スコア重み付け演算子weightを使用した場合、重みは無視されます

例12-4は、XMLExistsのXPathパラメータにおけるora:containsのコールを示しています。Oracle XML DBネームスペースを表すものとして、接頭辞oraを宣言するネームスペース宣言に注意してください。

例12-4 複雑な任意のテキスト問合せを使用したora:contains

SELECT id
  FROM purchase_orders_xmltype
  WHERE
    XMLExists(
      'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
       $d/purchaseOrder/comment
         [ora:contains(text(), "($lawns AND wild) OR flamingo") > 0]'
      PASSING doc AS "d");

関連項目:

ora:contains XPath関数の詳細は、「ora:contains XQuery関数」を参照してください。

CONTAINSおよびora:containsの比較

Oracle SQL関数containsおよびOracle XPath関数ora:containsでは、いずれもXML構造の検索と全文検索を組み合せることができます。これらの主な違いは次のとおりです。

Oracle SQL関数containsの特性は、次のとおりです。

  • 実行するにはCONTEXT索引が必要です。索引がない場合はエラーが発生します。

  • 索引検索を実行し、通常非常に高速です。

  • (Oracle SQL関数scoreを使用して)スコアを戻します。

  • ノードではなく文書(表の行)に基づいて検索を限定します。

  • XML構造ベースの投影(XML文書の一部を抽出)には使用できません

Oracle XPath関数ora:containsの特性は、次のとおりです。

  • 索引は不要ですが、索引を使用することでパフォーマンスを向上できます。

  • 非索引検索であるため、大量のリソースを消費する場合があります。

  • 格納方法および索引付け方法に関する考慮事項からアプリケーション・ロジックを分離しています。

  • スコアを戻しません

  • XML構造ベースの投影(XML文書の一部を抽出)に使用できます。

XML文書で、単純なXML構造の制約を指定している場合など、高速な索引ベースの全文検索を実行する場合は、Oracle SQL関数containsを使用します。XPathナビゲーションと組み合せた全文検索の柔軟性が必要な場合(索引がない場合も含めて)、または投影は必要でスコアは不要な場合は、Oracle XPath関数ora:containsを使用します。

CONTAINS SQL関数

この項の内容は次のとおりです。

SQL関数CONTAINSを使用した全文検索

Oracle SQL関数containsの2番目の引数text_queryは、全文検索を指定する文字列です。text_queryには、SQL/MMの全文標準に基づいた独自の言語があります。


関連項目:

  • ISO/IEC 13249-2:2000、「Information technology - Database languages - SQL Multimedia and Application Packages - Part 2: Full-Text」、International Organization For Standardization、2000

  • text_query言語の演算子の詳細は、『Oracle Textリファレンス』を参照してください。


この項の後半の例では、全文検索の優れた機能をいくつか示します。使用可能な2、3の演算子を使用します。例の問合せでは、テキスト索引(索引タイプCTXSYS.CONTEXT)を使用してVARCHAR2列(PURCHASE_ORDERS.doc)が検索されます。

全文ブール演算子AND、ORおよびNOT

text_query言語では、ANDORおよびNOTの任意の組合せがサポートされています。優先順位はカッコを使用して制御できます。ブール演算子は次のいずれの方法でも記述できます。

  • ANDORNOT

  • andornot

  • &, |, ~

NOTは単項演算子ではなくバイナリであることに注意してください。式alpha NOT(beta)alpha AND unary-not(beta)と同等で、unary-notは単項否定を表します。


関連項目:

containsおよびora:containsで使用できる演算子の詳細は、『Oracle Textリファレンス』を参照してください。

例12-5 単純なブール演算子を使用したCONTAINS問合せ

SELECT id FROM purchase_orders WHERE contains(doc, 'lawn AND wild') > 0;

例12-6 複雑なブールを使用したCONTAINS問合せ

SELECT id FROM purchase_orders
  WHERE contains(doc, '((lawn OR garden) AND (wild OR flooded)) NOT(flamingo)')
        > 0;

全文ステミング: $

text_query言語では、語幹検索がサポートされています。例12-7では、"lawns"と同じ言語語幹のワードが含まれるすべての文書が戻されるため、"lawn"または"lawns"が検索されます。ステミング演算子は、ドル記号($)で記述されます。

例12-7 ステミングを使用したCONTAINS問合せ

SELECT id FROM purchase_orders WHERE contains(doc, '$(lawns)') > 0;

ブールとステミング演算子の組合せ

例12-8に示すように、text_query言語の演算子を組み合せることができます。

例12-8 複雑な問合せ式を使用したCONTAINS問合せ

SELECT id FROM purchase_orders
  WHERE contains(doc, '($lawns AND wild) OR flamingo') > 0;

関連項目:

text_query演算子の完全なリストは、『Oracle Textリファレンス』を参照してください。

SCORE SQL関数

Oracle SQL関数scoreはOracle SQL関数containsに関連します。関数scoreは問合せ内のどの場所でも使用できます。これは関連性を測定するもので、大規模なドキュメント・セット全体を全文検索するときに特に有効です。関数scoreは通常、問合せ結果の一部として戻されるか、ORDER BY句で使用されるか、あるいはその両方です。

SCORE構文

score(label NUMBER) RETURN NUMBER

例12-9score(10)では、結果セット内の各行に対するスコアが戻されます。Oracle SQL関数scoreは、関数containsの特定のコールに関する結果セット内の行の関連性を戻します。scoreのコールは、LABEL(この場合は番号10)によってcontainsのコールにリンクされます。

例12-9 SCOREを使用した単純なCONTAINS問合せ

SELECT score(10), id FROM purchase_orders
  WHERE contains(doc, 'lawn', 10) > 0 AND score(10) > 2
  ORDER BY score(10) DESC;

関数scoreは、対応するcontainsで、テキスト索引で規定された一致規則に従ってtext_query引数がinput_textと一致しない場合は、常に0を戻します。containstext_queryinput_textと一致した場合、scoreは、0より大きく100以下の数値を戻します。この数値は、input_textに対するtext_queryの関連性を示します。数値が大きいほど、一致精度が高いことを意味します。

containstext_queryHASPATH演算子およびText Pathのみで構成されている場合、HASPATHは完全一致をテストするため、スコアは0または100になります。


関連項目:

スコアの計算方法の詳細は、『Oracle Textリファレンス』を参照してください。

CONTAINS検索の範囲の限定

Oracle SQL関数containsでは、デフォルトで文書全体が全文検索されます。前述の例で、構造による限定のない"lawn"の検索では、発注書内のどこかにワード"lawn"があるすべての発注書が検索されます。

XML構造を使用してcontains問合せを限定する方法は、次の3通りあります。

  • WITHIN

  • INPATH

  • HASPATH


注意:

この説明では、セクションXMLノードと同じであると考えてください。

WITHIN構造演算子

WITHIN演算子は、問合せをXML文書内のあるセクションに限定します。コメント・セクションのどこかにワード"lawn"が含まれている発注書を検索するには、WITHINを使用します。セクション名の大/小文字は区別されます。

例12-10 WITHIN

SELECT id FROM purchase_orders WHERE contains(DOC, 'lawn WITHIN comment') > 0;
ネストしたWITHIN

WITHINをネストすると、問合せをさらに限定できます。例12-11では、セクション"comment"内にワード"lawn"が含まれ、"lawn"の出現箇所がセクション"item"内にもあるすべての文書が検索されます。

例12-11 ネストしたWITHIN

SELECT id FROM purchase_orders
  WHERE contains(doc, '(lawn WITHIN comment) WITHIN item') > 0;
  

例12-11では行が戻されません。使用する発注書サンプルには、コメント内にワード"lawn"が含まれています。しかし、項目内の唯一のコメントは"Confirm this is electric"です。そのため、ネストしたWITHIN問合せでは行が戻されません。

WITHIN(属性の場合)

属性内を検索することもできます。例12-12では、purchaseOrder要素のorderDate属性にワード"10"が含まれているすべての発注書が検索されます。

例12-12 属性内のWITHIN

SELECT id FROM purchase_orders
  WHERE contains(doc, '10 WITHIN purchaseOrder@orderDate') > 0;
  

デフォルトでは、マイナス記号(-)は単語セパレータとして処理されます。つまり、"1999-10-20"は3つの単語、"1999"、"10"および"20"として処理されます。したがって、この問合せでは1行戻されます。

属性内のテキストは、主検索可能文書の一部ではありません。text_queryWITHIN purchaseOrder@orderDateで修飾せずに10を検索すると、行は戻されません。

属性は、ネストしたWITHINでは検索できません。

WITHINおよびAND

コメント・セクション内にlawnとelectricの2つのワードが含まれる発注書を検索するとします。purchaseOrderにはコメント・セクションが複数ある場合があります。このため、この問合せの作成方法は2通りあり、異なる結果となります。

両方のワードが含まれ、各ワードがあるコメント・セクション内に出現する発注書を検索する場合は、例12-13のような問合せを作成します。

例12-13 WITHINおよびAND: あるコメント・セクション内の2つのワード

SELECT id FROM purchase_orders
  WHERE contains(doc, '(lawn WITHIN comment) AND (electric WITHIN comment)') > 0;

purchaseOrderデータに対してこの問合せを実行すると、1行戻されます。この例ではカッコは必要ありませんが、使用すると問合せがわかりやすくなります。

両方のワードが含まれ、両方のワードが同じコメント内に出現する発注書を検索する場合は、例12-14のような問合せを作成します。

例12-14 WITHINおよびAND: 同じコメント内の2つのワード

SELECT id FROM purchase_orders
  WHERE contains(doc, '(lawn AND electric) WITHIN comment') > 0;

例12-14の問合せでは、行が戻されません。例12-15の問合せのように、lawn AND electricを囲むカッコを外すと、1行戻されます。

例12-15 WITHINおよびAND: カッコを付けない場合

SELECT id FROM purchase_orders
  WHERE contains(doc, 'lawn AND electric WITHIN comment') > 0;

演算子WITHINの優先順位はANDより高いため、例12-15例12-16と同じように解析されます。

例12-16 WITHINおよびAND: 演算子優先順位を示すカッコ

SELECT id FROM purchase_orders
  WHERE contains(doc, 'lawn AND (electric WITHIN comment)') > 0;
セクションの定義

前述の例では、セクション内を検索するためにWITHIN演算子を使用しました。次のセクションを指定できます。

  • パス・セクションまたはゾーン・セクション

    これは、ノードの子孫であるテキスト・ノードのすべてを、間を空白で区切って文書順に連結したものです。ノードからゾーン・セクションに変換するには、ノードをシリアライズしてすべてのタグを空白で置換する必要があります。パス・セクションの有効範囲と動作はゾーン・セクションと同じですが、パス・セクションでは INPATHおよびHASPATH構造演算子を使用した問合せがサポートされます。

  • フィールド・セクション

    ゾーン・セクションと同じですが、文書内の繰返しノードが空白で区切られて単一のセクションに連結されます。

  • 属性セクション

  • 特殊セクション(文または段落)


    関連項目:

    特殊セクションの詳細は、『Oracle Textリファレンス』を参照してください。

INPATH構造演算子

演算子WITHINは、構造による単純な限定をtext_queryで表す簡単で直観的な方法です。豊富なXML構造を使用する問合せでは、ネストしたWITHIN演算子のかわりに、演算子INPATHとText Pathを使用できます。

演算子INPATHでは、左側にtext_queryを記述し、右側にカッコで囲んだText Pathを記述します。例12-17では、パス/purchaseOrder/items/item/comment内で単語"electric"が含まれるpurchaseOrdersが検索されます。

例12-17 全文述語内の構造: INPATH

SELECT id FROM purchase_orders
  WHERE contains(doc, 'electric INPATH (/purchaseOrder/items/item/comment)') > 0;

例12-17の検索範囲は、Text Pathで示されたセクションです。例12-18の問合せでは例12-17の問合せよりも範囲の広いパスを使用しますが、同じように1行しか戻されません。

例12-18 全文述語内の構造: INPATH

SELECT id FROM purchase_orders
  WHERE contains(doc, 'electric INPATH (/purchaseOrder/items)') > 0;
Text Path

Text Pathの構文とセマンティクスは、W3CのXPath 1.0勧告に基づいています。単純なパス式はサポートされますが(省略構文のみ)、関数はサポートされません。以降の各例は、一般的な変形例を示しています。


関連項目:


例12-19では、属性partNumが"872-AA"と等しいitem要素の子であるcomment要素内にワード"electric"が含まれるすべての発注書が検索されます。つまり、ルート・ノードからいくつか下のレベルのitems要素の子です。

例12-19 複雑なパス式を使用したINPATH(1)

SELECT id FROM purchase_orders
  WHERE contains(doc, 'electric INPATH (//items/item[@partNum="872-AA"]/comment)')  
        > 0;

例12-20では、3番目のレベルであるitem要素(またはその子孫)内に単語"lawnmower"が含まれ、すべてのレベルにcomment要素があるすべての発注書が検索されます。この問合せでは1行戻されます。問合せの有効範囲はcomment要素ではなく、子孫としてcomment要素を持つ一連のitems要素であることに注意してください。

例12-20 複雑なパス式を使用したINPATH(2)

SELECT id FROM purchase_orders
  WHERE contains(doc, 'lawnmower INPATH (/*/*/item[.//comment])') > 0;
Text PathとXPathの比較

Text Path言語は、XPath言語と次の点で異なります。

  • すべてのXPath演算子がText Path言語に含まれているわけではありません。

  • XPathの組込み関数はText Path言語に含まれていません。

  • Text Path言語の演算子は大/小文字が区別されません。

  • フィルタ内(カッコ内)で=を使用した場合、一致はテキスト一致規則に従います。

    大/小文字の区別、正規化、ストップワードおよび空白の規則はテキスト索引の定義によって異なります。この相違を強調するために、この種類の等価性をこのマニュアルではテキスト等値と呼びます。

  • 名前空間サポートはText Path言語に含まれていません。

    名前空間接頭辞(存在する場合)など、要素名は文字列として処理されます。そのため、同じ名前空間URIにマップされている2つの異なる名前空間接頭辞は、Text Path言語では等価として処理されません。

  • Text Pathでは、コンテキストは常に文書のルート・ノードです。

    したがって、発注データpurchaseOrder/items/item/purchaseOrder/items/itemおよび./purchaseOrder/items/itemはすべて等価です。

  • 属性値内を検索する場合は、属性の直接の親を指定する必要があります(ワイルド・カードは使用できません)。

  • Text Pathはワイルド・カード(*)で終了できません。


関連項目:

Text Pathの文法については、「Text Path BNFの仕様」を参照してください。

ネストしたINPATH

INPATH式はネストできます。Text Pathのコンテキストは常にルート・ノードです。ネストしたINPATHの場合も変わりません。

例12-21では、任意のレベルのcomment要素にワード"electric"が含まれる発注書が検索されます。このワードの出現箇所は、最上位レベルpurchaseOrder要素の子であるitem要素内でもあります。

例12-21 ネストしたINPATH

SELECT id FROM purchase_orders
  WHERE contains(doc, 
                 '(electric INPATH (//comment)) INPATH (/purchaseOrder/items)')
        > 0;

このネストしたINPATH問合せは、例12-22に示すように、簡潔に記述できます。

例12-22 リライト済のネストしたINPATH

SELECT id FROM purchase_orders
  WHERE contains(doc, 'electric INPATH (/purchaseOrder/items//comment)') > 0;

HASPATH構造演算子

演算子HASPATHで使用するオペランドはText Pathのみで、カッコで囲み、右側に記述します。=述語などを使用して、特定のパス内に特定のセクションが含まれる文書を検索するときは、HASPATHを使用します。これは全文検索ではなく、パス検索です。セクションが存在するかどうかの確認、またはセクションの内容の照合はできますが、ワード検索はできません。データ型がXMLTypeの場合は、構造演算子HASPATHではなくSQL/XML関数XMLExistsの使用を考慮してください。

例12-23では、USPriceを持つ項目があるpurchaseOrdersが検索されます。

例12-23 単純なHASPATH

SELECT id FROM purchase_orders
  WHERE contains(DOC, 'HASPATH (/purchaseOrder//item/USPrice)') > 0;

例12-24では、"148.95"とテキスト等値であるUSPriceを持つ項目があるpurchaseOrdersが検索されます。


関連項目:

テキスト等値の説明は、「Text PathとXPathの比較」を参照してください。

例12-24 HASPATHの等値式

SELECT id FROM purchase_orders
  WHERE contains(doc, 'HASPATH (/purchaseOrder//item/USPrice="148.95")') > 0;

HASPATHは、INPATHなど他のcontains演算子と組み合せることができます。例12-25では、文書の任意の場所にワード electricを含み、148.95とテキスト等値であるUSPriceを持つitemがあり、かつpurchaseOrder属性orderDate内に10が含まれるpurchaseOrdersが検索されます。

例12-25 HASPATHと他の演算子の組合せ

SELECT id FROM purchase_orders
  WHERE contains(doc,
                 'electric
                  AND HASPATH (/purchaseOrder//item/USPrice="148.95")
                  AND 10 INPATH (/purchaseOrder/@orderDate)')
        > 0;

CONTAINS結果の投影

WHERE句内にcontains式を使用したSQL問合せの結果は常に、行のセット(および多くの場合score情報)、または問合せと一致する行全体の投影です。

contains式を満たす各XML文書の一部のみを戻すには、SQL/XML関数XMLQueryを使用します。この項の例では、XMLTypepurchase_orders_xmltypeが使用されます。

例12-26では、最上位レベルのpurchaseOrder要素の子孫であるcomment要素内にワード"electric"が含まれるpurchaseOrderが検索されます。各結果の行IDを戻すかわりに、XMLQueryを使用してcomment要素のみを戻します。

例12-26 CONTAINS問合せの結果の範囲指定

SELECT XMLQuery('declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
                 $d/purchaseOrder//comment'
                PASSING doc AS "d" RETURNING CONTENT) "Item Comment"
  FROM purchase_orders_xmltype
  WHERE CONTAINS(doc, 'electric INPATH (/purchaseOrder//comment)') > 0;

例12-26の結果は、要素comment2つのインスタンスです。関数containsは、comment要素内に単語"electric"が含まれている行(ID = 1の行)を示します。関数XMLQueryは文書内のその行にある要素commentのインスタンスをすべて抽出します。purchaseOrder要素には要素 commentのインスタンスが2つあり、問合せではその両方が戻されます。

これは希望の結果と異なる場合があります。contains式を満たすcomment要素の実例のみを問合せで戻す場合は、XMLQueryに渡されるXQuery式でその述語を繰り返す必要があります。これには、XPath関数ora:containsを使用します。例12-27に、これを示します。

例12-27 ora:containsを使用したCONTAINS問合せ結果の投影

SELECT XMLQuery('declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
                 $d/purchaseOrder/items/item/comment
                   [ora:contains(text(), "electric") > 0]'
                PASSING doc AS "d" RETURNING CONTENT) "Item Comment"
  FROM purchase_orders_xmltype
  WHERE CONTAINS(doc, 'electric INPATH (/purchaseOrder/items/item/comment)') > 0;

CONTEXT索引を使用した索引付け

この項の内容は次のとおりです。

CONTEXT索引の概要

汎用の全文索引タイプは、データベース・ユーザーCTXSYSが所有するCONTEXT索引タイプです。デフォルトの全文索引を作成するには、例12-28に示すように、通常のSQL CREATE INDEXコマンドを使用し、句INDEXTYPE IS CTXSYS.CONTEXTを追加します。

例12-28 表PURCHASE_ORDERSの単純なCONTEXT索引

CREATE INDEX po_index ON purchase_orders(doc)
  INDEXTYPE IS CTXSYS.CONTEXT;

全文索引を作成するときには、多数の選択肢があります。これらの選択肢は、索引付けプリファレンスとして表されます。索引付けプリファレンスを使用するには、例12-29に示すように、CREATE INDEXPARAMETERS句を追加します。

例12-29 パス・セクション・グループを使用したXMLType表の単純なCONTEXT索引

CREATE INDEX po_index ON purchase_orders(doc)
  INDEXTYPE IS CTXSYS.CONTEXT 
  PARAMETERS ('section group CTXSYS.PATH_SECTION_GROUP');

Oracle Textには、CTXCATCTXRULEなどの他の索引タイプが用意されていますが、この章では説明しません。


関連項目:

CONTEXT索引の詳細は、『Oracle Textリファレンス』を参照してください。

XMLType表のCONTEXT索引

CONTEXT索引は、テキストが含まれた任意のデータに対して作成できます。例12-28では、VARCHAR2列に対するCONTEXT索引が作成されます。型がCHARVARCHARVARCHAR2BLOBCLOBBFILEXMLTypeまたはURITypeの列にCONTEXT索引を作成する構文は同じです。例12-30では、XMLType型の列に対するCONTEXT索引が作成されます。セクション・グループはデフォルトでPATH_SECTION_GROUPに設定されます。

例12-30 XMLType列の単純なCONTEXT索引

CREATE INDEX po_index_xmltype ON purchase_orders_xmltype(doc)
  INDEXTYPE IS CTXSYS.CONTEXT;

XMLType表がある場合は、例12-31に示すように、オブジェクト構文を使用してCONTEXT索引を作成する必要があります。

例12-31 XMLType表の単純なCONTEXT索引

CREATE INDEX po_index_xmltype_table 
  ON purchase_orders_xmltype_table (OBJECT_VALUE)
  INDEXTYPE IS CTXSYS.CONTEXT;

例12-32に示すように、表を問合せできます。

例12-32 XMLType表のCONTAINS問合せ

SELECT XMLCast(XMLQuery(
                 'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
                  $p/purchaseOrder/@orderDate'
                 PASSING po.OBJECT_VALUE AS "p" RETURNING CONTENT)
               AS DATE) "Order Date"
  FROM purchase_orders_xmltype_table po
  WHERE contains(po.OBJECT_VALUE, 'electric INPATH (/purchaseOrder//comment)')
        > 0;
CONTEXT索引のメンテナンス

ほとんどの全文索引と同様に、CONTEXT索引は非同期です。索引付けされたデータが変更された場合、CONTEXT索引は、プロシージャをコールして索引を同期化するなど、なんらかの処理を行うまで変更されません。

CONTEXT索引は、時間経過とともに断片化する可能性があります。断片化した索引では通常より多くの領域が使用されるため、問合せが遅くなります。CONTEXT索引を最適化(断片化解消)する方法はたくさんあります。


関連項目:

CONTEXT索引のメンテナンスの詳細は、『Oracle Textリファレンス』を参照してください。

ロールおよび権限

CONTEXT索引の作成に特別な権限は必要ありません。プリファレンスの作成と削除、およびOracle TextのPL/SQLパッケージの使用には、CTXAPPロールが必要です。また、CTXSYSパッケージCTX_DDLに対するEXECUTE権限も必要です。

CONTAINSにおけるCONTEXT索引の効果

Oracle SQL関数containsを使用するには、CONTEXT型の索引を作成する必要があります。containsをコールしたとき、最初の引数で指定された列にCONTEXT型の索引がない場合は、エラーが発生します。

text_queryの構文およびセマンティクスは、CONTEXT索引の作成時に選択した内容によって異なります。次に例を示します。

  • ワードとしてカウントする対象

  • 最も一般的なワードを処理対象とするかどうか

  • 最も一般的なワード

  • テキスト検索で大/小文字を区別するかどうか

  • テキスト検索にキーワードの他にテーマ(概念)を含めることができるかどうか

CONTEXT索引プリファレンス

プリファレンスは、索引付けに関する選択肢の集合と考えることができます。プリファレンスには、セクション・グループ、データストア、フィルタ、ワードリスト、ストップリストおよび記憶域が含まれます。この項では、検索で大/小文字を区別するためのレクサー・プリファレンスの設定方法を示します。

プリファレンスを作成するには、プロシージャCTX_DDL.create_preference(またはCTX_DDL.create_stoplist)を使用します。プロシージャCTX_DDL.set_attributeを使用して新規プリファレンスの属性を設定することによって、そのプリファレンス・グループのデフォルトの選択肢をオーバーライドします。その後、CREATE INDEXPARAMETERS文字列内にpreference type preference_nameを含めることによって、CONTEXT索引でプリファレンスを使用します。

プリファレンスを作成すると、それを使用して索引をいくつでも作成できます。

検索で大/小文字を区別する方法

containsを使用した全文検索では、デフォルトで大/小文字が区別されません。つまり、文書内のワードに対してtext_query内のワードを照合する際に、大/小文字は考慮されません。ただし、セクション名および属性名は常に、大/小文字が区別されます

全文検索で大/小文字を区別する場合は、CONTEXT索引の作成時にそのように選択する必要があります。例12-33では1行戻されます。これは、text_query内の"HURRY"が、デフォルトの大/小文字が区別されない索引が設定されたpurchaseOrder内の"Hurry"と一致するためです。

例12-33 CONTAINS: デフォルトの大/小文字を区別しない一致

SELECT id FROM purchase_orders
  WHERE contains(doc, 'HURRY INPATH (/purchaseOrder/comment)') > 0;

例12-34では、属性mixed_caseTRUEに設定された新しいレクサー・プリファレンスmy_lexerが作成されます。この例では、printjoin文字も"-"、"!"、","に設定されます。CONTEXT索引の作成およびポリシーの作成に同じプリファレンスを使用できます。


関連項目:

レクサー属性の完全なリストは、『Oracle Textリファレンス』を参照してください。

例12-34 大/小文字混合のプリファレンスの作成

BEGIN
  CTX_DDL.create_preference(PREFERENCE_NAME  =>  'my_lexer',
                            OBJECT_NAME      =>  'BASIC_LEXER');
    
  CTX_DDL.set_attribute(PREFERENCE_NAME  =>  'my_lexer', 
                        ATTRIBUTE_NAME   =>  'mixed_case', 
                        ATTRIBUTE_VALUE  =>  'TRUE');
    
  CTX_DDL.set_attribute(PREFERENCE_NAME  =>  'my_lexer', 
                        ATTRIBUTE_NAME   =>  'printjoins', 
                        ATTRIBUTE_VALUE  =>  '-,!');
END;
/

例12-35では、新しいmy_lexerレクサー・プレファレンスを使用してCONTEXT索引が作成されます。ここではプレファレンスpreference-case-mixedが使用されます。

例12-35 PURCHASE_ORDERS表のCONTEXT索引、大/小文字混合

CREATE INDEX po_index ON purchase_orders(doc)
  INDEXTYPE IS CTXSYS.CONTEXT
  PARAMETERS('lexer my_lexer section group CTXSYS.PATH_SECTION_GROUP');

例12-33では、text_query内の"HURRY"がpurchaseOrder内の"Hurry"と一致しないため、行は戻されません。例12-36では、text_queryの"Hurry"がpurchaseOrder内のワード"Hurry"と正確に一致するため、1行戻されます。

例12-36 CONTAINS: 大/小文字混合の(正確な)一致

SELECT id FROM purchase_orders
  WHERE contains(doc, 'Hurry INPATH (/purchaseOrder/comment)') > 0;

セクション・グループの概要

CONTEXT索引の作成時に行う選択の1つにセクション・グループがあります。セクション・グループ・インスタンスは、セクション・グループ・タイプに基づいています。セクション・グループ・タイプでは、文書の構造の種類、およびその構造の索引付け方法(したがって検索方法)が指定されます。セクション・グループ・インスタンスによって、索引付けされる構造要素が指定される場合もあります。ほとんどのユーザーは、デフォルトのセクション・グループまたは事前定義のセクション・グループのいずれかを使用します。

セクション・グループ・タイプの選択

XMLの検索で有効なセクション・グループ・タイプは、次のとおりです。

  • PATH_SECTION_GROUP

    問合せでWITHININPATHおよびHASPATHを使用し、すべてのセクションを問合せの有効範囲とする場合はこのタイプを選択します。

  • XML_SECTION_GROUP

    問合せでWITHINを使用するが、INPATHおよびHASPATHは使用せず、明示的に定義されたセクションのみを問合せの有効範囲とする場合は、このタイプを選択します。XML_SECTION_GROUPセクション・グループ・タイプでは、ZONEセクションの他にFIELDセクションがサポートされます。状況によっては、FIELDセクションの方が問合せのパフォーマンスが大幅に向上する場合があります。

  • AUTO_SECTION_GROUP

    問合せでWITHINを使用するが、INPATHおよびHASPATHは使用せず、大部分のセクションを問合せの有効範囲とする場合は、このタイプを選択します。デフォルトでは、すべてのセクションが索引付けされます(問合せの限定に使用可能)。一部のセクションには索引付けしないことを指定できます(STOPセクションを定義)。

  • NULL_SECTION_GROUP

    XMLセクションを定義しない場合は、このタイプを選択します。

その他のセクション・グループ・タイプは、次のとおりです。

  • BASIC_SECTION_GROUP

  • HTML_SECTION_GROUP

  • NEWS_SECTION_GROUP

XMLの全文検索要件がある大部分のユーザーは、PATH_SECTION_GROUPを使用することをお薦めします。一部のユーザーには、FIELDセクションを使用するXML_SECTION_GROUPの使用が適している場合もあります。この選択によって、通常、問合せパフォーマンスが向上し、索引が小さくなりますが、フィールド化構造の文書に限定されます(検索可能なノードは、繰返しのないすべてのリーフ・ノードです)。


関連項目:

XML_SECTION_GROUPセクション・グループ・タイプの詳細は、『Oracle Textリファレンス』を参照してください。

セクション・グループの選択

索引で使用するセクション・グループを選択するときは、選択したセクション・グループ・タイプに基づいて、提供されているセクション・グループを選択するか、デフォルトを使用するか、または新しいセクション・グループを作成するかを選択できます。

セクション・グループ・タイプPATH_SECTION_GROUPAUTO_SECTION_GROUPおよびNULL_SECTION_GROUPには、提供されているセクション・グループがあります。提供されているセクション・グループの所有者はCTXSYSで、名前はセクション・グループ・タイプと同じです。たとえば、セクション・グループ・タイプPATH_SECTION_GROUPの提供されているセクション・グループはCTXSYS.PATH_SECTION_GROUPです。

セクション・グループ・タイプXML_SECTION_GROUPには提供されているセクション・グループがありません。これは、デフォルトのXML_SECTION_GROUPは空であり、無意味であるためです。セクション・グループ・タイプXML_SECTION_GROUPを使用する場合は、新しいセクション・グループを作成し、セクションとして組み込む各ノードを指定する必要があります。

XMLType型のデータにCONTEXT索引を作成した場合、デフォルトのセクション・グループは、提供されているセクション・グループCTXSYS.PATH_SECTION_GROUPです。データがVARCHARまたはCLOBの場合、デフォルトのセクション・グループはCTXSYS.NULL_SECTION_GROUPです。


関連項目:

独自のセクション・グループを作成する方法は、『Oracle Textリファレンス』を参照してください。

セクション・グループを索引と関連付けるには、例12-37のように、PARAMETERS文字列にsection group <section group name>を追加します。

例21-37 パス・セクション・グループを使用したpurchase_orders表の単純なCONTEXT索引

CREATE INDEX po_index ON purchase_orders(doc)
  INDEXTYPE IS CTXSYS.CONTEXT 
  PARAMETERS ('section group CTXSYS.PATH_SECTION_GROUP');

ora:contains XQuery関数

関数ora:containsは、SQL/XML関数XMLQueryXMLTableおよびXMLExistsのXQuery式の引数で使用するために、Oracleによって定義されたXQuery(XPath)関数です。

ora:containsを使用する場合は、Oracle XML DB名前空間xmlns:ora="http://xmlns.oracle.com/xdb"に接頭辞oraをマップする名前空間宣言も指定する必要があります。

関数ora:containsは数値を戻します。スコアは戻しませんtext_queryinput_textと一致した場合、正数を戻します。そうでない場合は0(ゼロ)を戻します。

XQuery関数ora:containsを使用した全文検索

ora:containsの引数text_queryは、全文検索を指定する文字列です。ora:containstext_querycontainstext_queryと同じですが、次の制限事項があります。

  • ora:containstext_queryには、構造演算子WITHININPATHまたはHASPATHを含めることができません

  • ora:containstext_queryには、スコア重み付け演算子weight(*)を含めることができますが、重みは無視されます

ora:containstext_queryに次のいずれかを含めた場合は、その問合せでCONTEXT索引を使用できません

  • スコアベースの演算子MINUS(-)またはthreshold(>)

  • 選択的なコーパスベースの拡張演算子FUZZY(?)またはsoundex(!)

例12-4に、ブール演算子と$(ステミング)を任意に組み合せた全文検索を示します。


関連項目:

  • 全文演算子の詳細は、「関数CONTAINSを使用した全文検索」を参照してください。

  • containsおよびora:containsで使用できる演算子の完全なリストは、『Oracle Textリファレンス』を参照してください。


一致規則は、ポリシーpolicy_owner.policy_nameで定義されます。policy_ownerの指定がない場合、ポリシー所有者はデフォルトで現行ユーザーに設定されます。policy_namepolicy_ownerの両方が指定されない場合、ポリシーはデフォルトでCTXSYS.DEFAULT_POLICY_ORACONTAINSに設定されます。

ora:contains問合せの範囲の限定

XPath式でora:containsを使用する場合、有効範囲は引数input_textで定義されます。この引数は、現在のXPathのコンテキスト内で評価されます。結果が単一のテキスト・ノードまたは属性の場合、そのノードはora:contains検索のターゲットです。input_textが単一のテキスト・ノードまたは属性に対して評価されない場合は、エラーが発生します。

ポリシーによって、ora:containsの一致規則が決定されます。ora:containsのデフォルト・ポリシーに関連付けられたセクション・グループの型はNULL_SECTION_GROUPです。

ora:containsはXPath式のどこでも使用でき、そのinput_text引数は単一のテキスト・ノードまたは属性に対して評価されるXPath式です。

ora:contains結果の投影

各XML文書の一部のみを戻す場合は、関数XMLQueryを使用してノード順序を投影し、その結果にXMLCastを適用してノードのスカラー値を投影します。

例12-38では、ワード"lawn"を含むcommentがある各発注書のorderDateが戻されます。

例12-38 XMLQueryおよびXMLExistsでのora:containsの使用

SELECT XMLCast(XMLQuery(
                 'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
                  $d/purchaseOrder/@orderDate'
                 PASSING doc AS "d" RETURNING CONTENT)
               AS DATE) "Order date"
  FROM purchase_orders_xmltype
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment
             [ora:contains(text(), "($lawns AND wild) OR flamingo") > 0]'
          PASSING doc AS "d");

関数XMLExistsは、ワード"lawn"の入ったcommentpurchaseOrder要素に含まれる行(文書)に結果を限定します。その後、関数XMLQueryが、それらのpurchaseOrder要素から属性orderDateの値を戻します。関数XMLCastは、この結果をSQL DATE値としてキャストします。

//commentが抽出されていれば、WHERE句と一致したコメントのみではなく、両方のコメントがサンプル・ドキュメントから戻されます。

ora:contains問合せのポリシー

列のCONTEXT索引によって、その列に対するcontains問合せのセマンティクスが決まります。ora:containsはサポートしている索引に依存しないため、ora:contains問合せを実行するときは、同様の選択肢を多数提供する他の方法を検索する必要があります。ポリシーは、ora:contains問合せに関連付けることができるプリファレンスの集合であり、索引付けに関する選択でcontainsユーザーに提供される内容と同じようなセマンティクス制御を提供します。

ora:contains問合せのポリシーの概要

Oracle SQL関数containsを使用すると、索引付けのプリファレンスが問合せのセマンティクスに影響を与えます。プロシージャCTX_DDL.create_preference(またはCTX_DDL.create_stoplist)を使用して、プリファレンスを作成します。プロシージャCTX_DDL.set_attributeを使用して新規プリファレンスの属性を設定することによって、デフォルトの選択肢をオーバーライドします。その後、CREATE INDEXPARAMETERS文字列内にpreference_type preference_nameを含めることによって、CONTEXT索引でプリファレンスを使用します。

ora:containsにはサポートしている索引がないため、問合せにプリファレンスを適用するには、別のメカニズムが必要です。そのメカニズムはポリシーであり、プリファレンスの集合で構成されます。これはora:containsのパラメータとして使用されます。

ポリシーの例: 提供されているストップリスト

例12-39では、空のストップワード・リストを持つポリシーが作成されます。

例12-39 ora:containsで使用するポリシーの作成

BEGIN
  CTX_DDL.create_policy(POLICY_NAME  =>  'my_nostopwords_policy',
                        STOPLIST     =>  'CTXSYS.EMPTY_STOPLIST');
END;
/

わかりやすいように、このポリシーは空のストップリストで構成されており、所有者はユーザーCTXSYSです。このポリシーに組み込む新しいストップリストを作成するか、またはCONTEXT索引用に作成したストップリスト(またはレクサー)定義を再利用できます。

最も一般的なワード(ストップワード)も含めてすべてのワードを検索するには、ora:contains式でこのポリシーを参照してください。例12-40では、"is"はデフォルトでストップワードであり問合せできないため、コメントは戻されません。

例12-40 ora:containsを使用したストップワードの検索

SELECT id FROM purchase_orders_xmltype
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment[ora:contains(text(), "is") > 0]'
          PASSING doc AS "d");

例12-41では、空のストップワード・リストを指定するために、例12-39で作成されたポリシーが使用されます。この問合せでは"is"が検索され、コメントが1つ戻されます。

例12-41 ora:containsおよびポリシーmy_nostopwords_policyを使用したストップワードの検索

SELECT id FROM purchase_orders_xmltype
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment
             [ora:contains(text(), "is", "MY_NOSTOPWORDS_POLICY") > 0]'
          PASSING doc AS "d");

例12-41では、ポリシーmy_nostopwords_policyを使用します。例12-39では、このポリシーの名前は暗黙的にすべて大文字になります。XPathでは大/小文字が区別されるため、XPath述語内でこのポリシーは、my_nostopwords_policyではなくMY_NOSTOPWORDS_POLICYのように、すべて大文字を使用して記述する必要があります。

ora:containsでのポリシーの効果

ora:containsポリシーは、text_queryの一致セマンティクスに影響を与えます。ora:containsポリシーには、レクサー、ストップリスト、ワードリスト・プリファレンスまたはこれらの組合せが含まれる場合があります。CONTEXT索引の作成に使用できる他のプリファレンスは、ora:containsには適用できません。プリファレンスの効果は次のとおりです。

  • ワードリスト・プリファレンスによって、ステミング演算子のセマンティクスが改良されます。

  • ストップリスト・プリファレンスによって、一般的であるために索引付け(検索可能に)しないワードが定義されます。

  • レクサー・プリファレンスによって、ワードのトークン化および一致方法が定義されます。たとえば、ワードの一部としてカウントする文字および一致で大/小文字を区別するかどうかが定義されます。


関連項目:


ポリシーの例: ユーザー定義レクサー

特定のワードが含まれる文書を検索する場合は通常、検索で大/小文字を区別しない方法が使用されます。大/小文字を区別する検索を実行すると、予期した結果を得られない場合があリます。たとえば、"baby monitor"という句が含まれるpurchaseOrdersを検索する場合、使用しているサンプル文書では"Baby Monitor"と記述されているというだけの理由で、この文書の検索が行われないとは予測しません。

ora:containsを使用した全文検索では、デフォルトで大/小文字が区別されません。ただし、セクション名および属性名は常に、大/小文字が区別されます。

全文検索で大/小文字を区別する場合は、ポリシーの作成時にそのように選択する必要があります。次の手順を使用します。

  1. プロシージャCTX_DDL.create_preference(またはCTX_DDL.create_stoplist)を使用して、プリファレンスを作成します。

  2. プロシージャCTX_DDL.set_attributeを使用して新規プリファレンスの属性を設定することによって、そのプリファレンス・オブジェクトのデフォルトの選択肢をオーバーライドします。

  3. そのプリファレンスをCTX_DDL.create_policyのパラメータとして使用します。

  4. 問合せではora:containsの3番目の引数として、ポリシー名を使用します。

プリファレンスを作成した後は、そのプリファレンスを他のポリシーまたはCONTEXT索引の定義で再利用できます。すべてのora:contains問合せで、任意のポリシーを使用できます。

例12-42では1行戻されます。これは、text_query内の"HURRY"が、デフォルトの大/小文字が区別されない索引が設定されたpurchaseOrder内の"Hurry"と一致するためです。

例12-42 ora:contains、デフォルトの大/小文字の区別

SELECT id FROM purchase_orders_xmltype
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment[ora:contains(text(), "HURRY") > 0]'
          PASSING doc AS "d");

例12-43では、属性mixed_caseTRUEに設定された新しいレクサー・プリファレンスmy_lexerが作成されます。この例では、printjoin文字も"-"、"!"、","に設定されます。CONTEXT索引の作成およびポリシーの作成に同じプリファレンスを使用できます。


関連項目:

レクサー属性の完全なリストは、『Oracle Textリファレンス』を参照してください。

例12-43 大/小文字混合のプリファレンスの作成

BEGIN
  CTX_DDL.create_preference(PREFERENCE_NAME  =>  'my_lexer',
                            OBJECT_NAME      =>  'BASIC_LEXER');
  CTX_DDL.set_attribute(PREFERENCE_NAME  =>  'MY_LEXER', 
                        ATTRIBUTE_NAME   =>  'MIXED_CASE', 
                        ATTRIBUTE_VALUE  =>  'TRUE');
  CTX_DDL.set_attribute(PREFERENCE_NAME  =>  'my_lexer', 
                        ATTRIBUTE_NAME   =>  'printjoins', 
                        ATTRIBUTE_VALUE  =>  '-,!');
END;
/

例12-44では、新規ポリシーmy_policyが作成され、レクサーのみが指定されます。他のプリファレンスはすべてデフォルトに設定されます。例12-44では、preference-case-mixedが使用されます。

例12-44 大/小文字混合の(大/小文字が区別されない)ポリシーの作成

BEGIN
  CTX_DDL.create_policy(POLICY_NAME  => 'my_policy',
                        LEXER        => 'my_lexer');
END;
/

例12-45では、問合せで新規ポリシーが使用されます。text_query内の"HURRY"はpurchaseOrder内の"Hurry"と一致しないため、行は戻されません。

例12-45 ora:contains、大/小文字の区別(1)

SELECT id FROM purchase_orders_xmltype
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment
             [ora:contains(text(), "HURRY", "my_policy") > 0]'
          PASSING doc AS "d");

例12-46では、text_queryの"Hurry"がcomment要素内のテキスト"Hurry"と正確に一致するため、1行戻されます。

例12-46 ora:contains、大/小文字の区別(2)

SELECT id FROM purchase_orders_xmltype
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment[ora:contains(text(), "Hurry") > 0]'
          PASSING doc AS "d");

ポリシーのデフォルト

ora:containsのポリシーの引数はオプションです。省略した場合、問合せではデフォルトのポリシーCTXSYS.DEFAULT_POLICY_ORACONTAINSが使用されます。

ora:containsで使用するポリシーを作成するときは、すべてのプリファレンスを指定する必要はありません。たとえば、例12-44では、レクサー・プリファレンスのみが指定されていました。指定されないプリファレンスについては、CREATE_POLICYによって次のデフォルトのプリファレンスが使用されます。

  • CTXSYS.DEFAULT_LEXER

  • CTXSYS.DEFAULT_STOPLIST

  • CTXSYS.DEFAULT_ WORDLIST

ポリシーの作成では、CONTEXT索引の作成が索引メタデータのコピー・セマンティクスに従うのと同様に、プリファレンスとその属性のコピー・セマンティクスに従います。

ora:containsのパフォーマンス

ora:containsのXPath関数は、サポートしている索引に依存しません。ora:containsは非常に柔軟性があります。しかし、索引のない大量のデータを検索する場合、大量のリソースを消費することもあります。この項では、XPath関数ora:containsを使用するXPath式が含まれる問合せのパフォーマンスを最大にする方法を説明します。


注意:

ファンクション索引はXML問合せの高速化にも非常に有効ですが、一般的にテキスト問合せには適用できません。

この項の例では、表purchase_orders_xmltype_bigが使用されます。この表には、purchase_orders_xmltypeと同じ表構造とXML Schemaがありますが、約1,000行が格納されています。各行には、(id列に)一意のIDと、/purchaseOrder/items/item/comment内に少しずつ異なるテキストが含まれています。示されている実行計画は、SQL*PlusコマンドAUTOTRACEを使用して生成されたものです。実行計画は、SQLコマンドTRACEおよびTKPROFを使用しても生成できます。コマンドAUTOTRACEtraceおよびtkprofについては、この章で説明しません。

この項の内容は次のとおりです。

問合せでの1次フィルタの使用

ora:containsは、処理のコストが比較的かかるため、可能な場合は1次フィルタを組み込んだ問合せの作成をお薦めします。この結果、ora:containsで処理される行数が最小化されます。

例12-47では、実行計画からわかるように、表内の各行が検査(全表スキャン)されます。この例で、ora:containsは各行に対して評価されます。

例12-47 大規模な表のora:contains

SELECT id FROM purchase_orders_xmltype_big
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment[ora:contains(text(), "constitution") > 0]'
          PASSING doc AS "d");
Execution Plan
------------------------------------------------------------------------------------------------
| Id | Operation                       | Name                      |Rows|Bytes|Cost(%CPU)| Time|
------------------------------------------------------------------------------------------------
|   0| SELECT STATEMENT                |                           |  32|64480|686(38)|00:00:09|
|*  1| FILTER                          |                           |    |     |       |        |
|   2|TABLE ACCESS FULL                |PURCHASE_ORDERS_XMLTYPE_BIG|1161|2284K| 140(3)|00:00:02| 
|*  3|COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE    |    |     |       |        |
------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter( EXISTS (SELECT 0 FROM TABLE() "KOKBF$" WHERE
              SYS_XMLCONTAINS(SYS_XQ_UPKXML2SQL(SYS_XQEXVAL(SYS_XQEXTRACT(SYS_XQCON2SEQ(VALUE(KOKBF$)),
              '/comment/text()'),1,50),50,1,0),'constitution')>0))
   3 - filter(SYS_XMLCONTAINS(SYS_XQ_UPKXML2SQL(SYS_XQEXVAL(SYS_XQEXTRACT(SYS_XQCON2SEQ(VALUE(KOKBF$)),
              '/comment/text()'),1,50),50,1,0),'constitution')>0)
 
Note
-----
   - dynamic sampling used for this statement

例12-48のように、列idに対して索引を作成し、例12-49のように、選択性の高い述語idを問合せに追加すると、実行計画に示すように、索引idによって実行処理が起動されます。関数ora:containsは、idが5未満の行でのみ実行されます。

例12-48 IDのBツリー索引

CREATE INDEX id_index ON purchase_orders_xmltype_big(id);

例12-49 大規模な表のora:contains(追加の述語を使用)

SELECT id FROM purchase_orders_xmltype_big
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/comment[ora:contains(text(), "constitution") > 0]'
          PASSING doc AS "d")
    AND id > 5;
Execution Plan
-----------------------------------------------------------------------------------------------
| Id  | Operation                   | Name                      |Rows| Bytes |Cost(%CPU)| Time|
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                           |  1 |  2015 |8  (13)|00:00:01|
|*  1 |  TABLE ACCESS BY INDEX ROWID|PURCHASE_ORDERS_XMLTYPE_BIG|  1 |  2015 |8  (13)|00:00:01|
|*  2 |   INDEX RANGE SCAN          |ID_INDEX                   | 10 |       |2   (0)|00:00:01|
-----------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter(EXISTSNODE(SYS_MAKEXML("PURCHASE_ORDERS_XMLTYPE_BIG"."SYS_NC00003$
       "),'/purchaseOrder/items/item/comment[ora:contains(text(), "constitution") >
              0]','xmlns:ora="http://xmlns.oracle.com/xdb"')=1)
   2 - access("ID">5)
 
Note
-----
   - dynamic sampling used for this statement

XPathリライトおよびCONTEXT索引

Oracle Databaseでは、XPath式を使用する問合せを最適化することがあります。このXPathリライトは、問合せの最適化の一部として自動的に行われます。

Oracle XQuery関数ora:containsはサポートしている索引に依存しませんが、XPathリライトが行われる際には多くの場合、高いパフォーマンスを得るためにora:containsは既存のCONTEXT索引を使用します。


関連項目:


例12-50 ora:containsでの"electric"の検索

SELECT id FROM purchase_orders_xmltype
  WHERE XMLExists(
          'declare namespace ora = "http://xmlns.oracle.com/xdb"; (: :)
           $d/purchaseOrder/items/item/comment
             [ora:contains(text(), "electric") > 0]'
          PASSING doc AS "d");

例12-50のXPath式の単純評価では、列docの各セルがその式と一致しているかどうかをテストします。

しかし、docがXML Schemaに基づいており、purchaseOrder文書がオブジェクト・リレーショナル表に物理的に格納されている場合は、列comment(存在する場合)に直接アクセスし、そこで各セルが"electric"と一致しているかどうかをテストするほうが効率的です。

ora:containsの最初の引数が単一のリレーショナル列にマップされている場合、完全なXPath式をXML文書全体に適用するかわりに、ora:containsをその列に適用することができます。この結果、索引が含まれていない場合でも、問合せのパフォーマンスが大幅に向上します。

ora:containsCONTEXT索引を持つ列にマップするテキスト・ノードまたは属性とともに使用している場合、その索引を基礎となる列のデータに適用することがあります。CONTEXT索引をオブジェクト・リレーショナルXMLTypeデータとともに使用するには、次の両方の条件を満たす必要があります。

  • ora:containsのターゲット(input_text)は、親ノードが列にマップされている単一のテキスト・ノード、または列にマップされている属性の必要があります。列は(Ordered Collection Table内の列も含めて)、単一のリレーショナル列である必要があります。

  • 「ora:contains問合せのポリシー」で説明したように、索引付けに関する選択内容(contains用)およびポリシーの選択内容(ora:contains用)は問合せのセマンティクスに影響を与えます。単純な違いは、索引ベースのcontainsでは大/小文字が区別される検索であるのに対して、ora:containsでは大/小文字が区別されない検索が指定される点です。ora:containsおよびリライトされたcontainsのセマンティクスが同じになるように、ora:containsのポリシーがCONTEXT索引の索引に関する選択内容と正確に一致する必要があります。

ora:containsのポリシーとCONTEXTの索引の両方で、NULL_SECTION_GROUPセクション・グループ・タイプを使用する必要もあります。ora:containsポリシーのデフォルトのセクション・グループはctxsys.NULL_SECTION_GROUPです。

最後に、CONTEXT索引は通常非同期です。ワード"dog"が含まれる新しい文書を追加した場合にCONTEXT索引を同期化しないと、"dog"のcontains問合せでその文書が戻されません。しかし、同じデータに対するora:contains問合せでは戻されます。ora:containsとリライトされたcontainsで常に同じ結果が戻されるようにするには、PARAMETERS文字列にTRANSACTIONALキーワードを指定してCONTEXT索引を作成します。


関連項目:

パラメータTRANSACTIONALを指定してALTER INDEXを使用してトランザクション型のCONTEXT索引を作成する方法については、『Oracle Textリファレンス』を参照してください

XMLQueryXMLTableまたはXMLExistsを使用した問合せ。XPathにはora:containsが含まれ、ora:containsポリシーがCONTEXT索引の索引に関する選択内容と正確に一致し、次のいずれかの条件が当てはまる場合、XPathリライト対象とみなすことができます。

  • XMLデータはobject-relationallyに格納されます。最初のora:contains引数(input_text)が、親ノードが単一のリレーショナル列にマップされている単一のテキスト・ノードであるか、または単一のリレーショナル列にマップされている属性である場合。その列には、トランザクション型のCONTEXT索引があります。

  • XMLデータがXMLIndex索引によって索引付けされたバイナリXMLで、非構造化XMLIndexコンポーネントのパス表VALUE列または構造化XMLIndexコンポーネントのスカラー値列にCONTEXT索引があります。

    CONTEXT索引が非トランザクション型の場合は、XQuery拡張式プラグマora:use_text_indexを使用して、CONTEXT索引の使用を強制する必要もあります。例12-51に、これを示します。

例12-51 XQueryプラグマora:use_text_indexをora:containsとともに使用する

CREATE INDEX po_otext_ix ON my_path_table (VALUE) INDEXTYPE IS CTXSYS.CONTEXT;
 
EXPLAIN PLAN FOR
  SELECT DISTINCT XMLCast(XMLQuery('$p/PurchaseOrder/ShippingInstructions/address'
                                   PASSING po.OBJECT_VALUE AS "p" RETURNING CONTENT)
                          AS VARCHAR2(256)) "Address"
    FROM po_binxml po
    WHERE XMLExists(
            '$p/PurchaseOrder/ShippingInstructions/address
               [(# ora:use_text_index #) {ora:contains(., "$(Fortieth)")} > 0]'
            PASSING po.OBJECT_VALUE AS "p");

----------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name                         | Rows | Bytes|Cost (%CPU)| Time   |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                              |    1 | 3046 |   12  (17)|00:00:01|
|   1 |  SORT GROUP BY                       |                              |    1 | 3524 |           |        |
|*  2 |   TABLE ACCESS BY INDEX ROWID BATCHED| MY_PATH_TABLE                |    2 | 7048 |    3   (0)|00:00:01|
|*  3 |    INDEX RANGE SCAN                  | SYS89559_PO_XMLINDE_PIKEY_IX |    1 |      |    2   (0)|00:00:01|
|   4 |  HASH UNIQUE                         |                              |    1 | 3046 |   12  (17)|00:00:01|
|   5 |   NESTED LOOPS                       |                              |    1 | 3046 |    8  (13)|00:00:01|
|   6 |    SORT UNIQUE                       |                              |    1 | 3034 |    6   (0)|00:00:01|
|*  7 |     TABLE ACCESS BY INDEX ROWID      | MY_PATH_TABLE                |    1 | 3034 |    6   (0)|00:00:01|
|*  8 |      DOMAIN INDEX                    | PO_OTEXT_IX                  |      |      |    4   (0)|00:00:01|
|   9 |    TABLE ACCESS BY USER ROWID        | PO_BINXML                    |    1 |   12 |    1   (0)|00:00:01|
----------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter(SYS_XMLI_LOC_ISNODE("SYS_P3"."LOCATOR")=1)
   3 - access("SYS_P3"."RID"=:B1 AND "SYS_P3"."PATHID"=HEXTORAW('6F7F'))
   7 - filter("SYS_P1"."PATHID"=HEXTORAW('6F7F') AND SYS_XMLI_LOC_ISNODE("SYS_P1"."LOCATOR")=1)
   8 - access("CTXSYS"."CONTAINS"("SYS_P1"."VALUE",'$(Fortieth)')>0)
 
Note
-----
   - dynamic sampling used for this statement (level=2)
 
28 rows selected.

Text Path BNFの仕様

HasPathArg           ::=    LocationPath
                         |  EqualityExpr  
InPathArg            ::=    LocationPath 
LocationPath         ::=    RelativeLocationPath
                         |  AbsoluteLocationPath 
AbsoluteLocationPath ::=    ("/" RelativeLocationPath)
                         |  ("//" RelativeLocationPath) 
RelativeLocationPath ::=    Step
                         |  (RelativeLocationPath "/" Step)
                         |  (RelativeLocationPath "//" Step) 
Step                 ::=    ("@" NCName)
                         |  NCName
                         |  (NCName Predicate)
                         |  Dot
                         |  "*" 
Predicate            ::=    ("[" OrExp "]")
                         |  ("[" Digit+ "]") 
OrExpr               ::=    AndExpr
                         |  (OrExpr "or" AndExpr) 
AndExpr              ::=    BooleanExpr
                         |  (AndExpr "and" BooleanExpr) 
BooleanExpr          ::=    RelativeLocationPath
                         |  EqualityExpr
                         |  ("(" OrExpr ")")
                         |  ("not" "(" OrExpr ")") 
EqualityExpr         ::=    (RelativeLocationPath "=" Literal)
                         |  (Literal "=" RelativeLocationPath)
                         |  (RelativeLocationPath "=" Literal)
                         |  (Literal "!=" RelativeLocationPath)
                         |  (RelativeLocationPath "=" Literal)
                         |  (Literal "!=" RelativeLocationPath) 
Literal              ::=    (DoubleQuote [~"]* DoubleQuote)
                         |  (SingleQuote [~']* SingleQuote) 
NCName               ::=    (Letter |  Underscore) NCNameChar* 
NCNameChar           ::=    Letter
                         |  Digit
                         |  Dot
                         |  Dash
                         |  Underscore 
Letter               ::=    ([a-z] | [A-Z]) 
Digit                ::=    [0-9] 
Dot                  ::=    "." 
Dash                 ::=    "-" 
Underscore           ::=    "_" 

全文XMLのサポートの例

この項の内容は次のとおりです。

発注書のXML文書、po001.xml

例12-52 発注書のXML文書、po001.xml

<?xml version="1.0" encoding="UTF-8"?>
<purchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xsi:noNamespaceSchemaLocation="xmlschema/po.xsd" 
               orderDate="1999-10-20">
  <shipTo country="US">
    <name>Alice Smith</name>
    <street>123 Maple Street</street>
    <city>Mill Valley</city>
    <state>CA</state>
    <zip>90952</zip>
  </shipTo>
  <billTo country="US">
    <name>Robert Smith</name>
    <street>8 Oak Avenue</street>
    <city>Old Town</city>
    <state>PA</state>
    <zip>95819</zip>
  </billTo>
  <comment>Hurry, my lawn is going wild!</comment>
  <items>
    <item partNum="872-AA">
      <productName>Lawnmower</productName>
      <quantity>1</quantity>
      <USPrice>148.95</USPrice>
      <comment>Confirm this is electric</comment>
    </item>
    <item partNum="926-AA">
      <productName>Baby Monitor</productName>
      <quantity>1</quantity>
      <USPrice>39.98</USPrice>
      <shipDate>1999-05-21</shipDate>
    </item>
  </items>
</purchaseOrder>

CREATE TABLE文

例12-53 表PURCHASE_ORDERSの作成

CREATE TABLE purchase_orders (id NUMBER, doc VARCHAR2(4000));

INSERT INTO purchase_orders (id, doc)
  VALUES (1,
          '<?xml version="1.0" encoding="UTF-8"?>
           <purchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                          xsi:noNamespaceSchemaLocation="xmlschema/po.xsd"
                          orderDate="1999-10-20">
             <shipTo country="US">
               <name>Alice Smith</name>
               <street>123 Maple Street</street>
               <city>Mill Valley</city>
               <state>CA</state>
               <zip>90952</zip>
             </shipTo>
             <billTo country="US">
               <name>Robert Smith</name>
               <street>8 Oak Avenue</street>
               <city>Old Town</city>
               <state>PA</state>
               <zip>95819</zip>
             </billTo>
             <comment>Hurry, my lawn is going wild!</comment>
             <items>
               <item partNum="872-AA">
                 <productName>Lawnmower</productName>
                 <quantity>1</quantity>
                 <USPrice>148.95</USPrice>
                 <comment>Confirm this is electric</comment>
               </item>
               <item partNum="926-AA">
                 <productName>Baby Monitor</productName>
                 <quantity>1</quantity>
                 <USPrice>39.98</USPrice>
                 <shipDate>1999-05-21</shipDate>
               </item>
             </items>
           </purchaseOrder>');
COMMIT;

例12-54 表PURCHASE_ORDERS_XMLTYPEの作成

CREATE TABLE purchase_orders_xmltype (id  NUMBER, doc XMLType);

INSERT INTO purchase_orders_xmltype (id, doc)
  VALUES (1,
          XMLTYPE ('<?xml version="1.0" encoding="UTF-8"?>
                     <purchaseOrder
                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                       xsi:noNamespaceSchemaLocation="po.xsd"
                       orderDate="1999-10-20">
                       <shipTo country="US">
                         <name>Alice Smith</name>
                         <street>123 Maple Street</street>
                         <city>Mill Valley</city>
                         <state>CA</state>
                         <zip>90952</zip>
                       </shipTo>
                       <billTo country="US">
                         <name>Robert Smith</name>
                         <street>8 Oak Avenue</street>
                         <city>Old Town</city>
                         <state>PA</state>
                         <zip>95819</zip>
                       </billTo>
                       <comment>Hurry, my lawn is going wild!</comment>
                       <items>
                         <item partNum="872-AA">
                           <productName>Lawnmower</productName>
                           <quantity>1</quantity>
                           <USPrice>148.95</USPrice>
                           <comment>Confirm this is electric</comment>
                         </item>
                         <item partNum="926-AA">
                           <productName>Baby Monitor</productName>
                           <quantity>1</quantity>
                           <USPrice>39.98</USPrice>
                           <shipDate>1999-05-21</shipDate>
                         </item>
                       </items>
                   </purchaseOrder>'));
COMMIT;

例12-55 表PURCHASE_ORDERS_XMLTYPE_TABLEの作成

CREATE TABLE purchase_orders_xmltype_table OF XMLType;

INSERT INTO purchase_orders_xmltype_table
  VALUES (
    XMLType ('<?xml version="1.0" encoding="UTF-8"?>              <purchaseOrder 
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:noNamespaceSchemaLocation="xmlschema/po.xsd"
                orderDate="1999-10-20">
                <shipTo country="US">
                  <name>Alice Smith</name>
                  <street>123 Maple Street</street>
                  <city>Mill Valley</city>
                  <state>CA</state>
                  <zip>90952</zip>
                </shipTo>
                <billTo country="US">
                  <name>Robert Smith</name>
                  <street>8 Oak Avenue</street>
                  <city>Old Town</city>
                  <state>PA</state>
                  <zip>95819</zip>
                </billTo>
                <comment>Hurry, my lawn is going wild!</comment>
                <items>
                  <item partNum="872-AA">
                    <productName>Lawnmower</productName>
                    <quantity>1</quantity>
                    <USPrice>148.95</USPrice>
                    <comment>Confirm this is electric</comment>
                  </item>
                  <item partNum="926-AA">
                    <productName>Baby Monitor</productName>
                    <quantity>1</quantity>
                    <USPrice>39.98</USPrice>
                    <shipDate>1999-05-21</shipDate>
                  </item>
                </items>
              </purchaseOrder>'));
COMMIT;

全文検索する発注書XML Schemaの例

例12-56 全文検索する発注書XML Schemaの例

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:annotation>
    <xsd:documentation xml:lang="en">
      Purchase order schema for Example.com.
      Copyright 2000 Example.com. All rights reserved.
    </xsd:documentation>
  </xsd:annotation>
  <xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
  <xsd:element name="comment" type="xsd:string"/>
  <xsd:complexType name="PurchaseOrderType">
    <xsd:sequence>
      <xsd:element name="shipTo" type="USAddress"/>
      <xsd:element name="billTo" type="USAddress"/>
      <xsd:element ref="comment" minOccurs="0"/>
      <xsd:element name="items" type="Items"/>
    </xsd:sequence>
    <xsd:attribute name="orderDate" type="xsd:date"/>
  </xsd:complexType>
  <xsd:complexType name="USAddress">
    <xsd:sequence>
      <xsd:element name="name" type="xsd:string"/>
      <xsd:element name="street" type="xsd:string"/>
      <xsd:element name="city" type="xsd:string"/>
      <xsd:element name="state" type="xsd:string"/>
      <xsd:element name="zip" type="xsd:decimal"/>
    </xsd:sequence>
    <xsd:attribute name="country" type="xsd:NMTOKEN" fixed="US"/>
  </xsd:complexType>
  <xsd:complexType name="Items">
    <xsd:sequence>
      <xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="productName" type="xsd:string"/>
            <xsd:element name="quantity">
              <xsd:simpleType>
                <xsd:restriction base="xsd:positiveInteger">
                  <xsd:maxExclusive value="100"/>
                </xsd:restriction>
              </xsd:simpleType>
            </xsd:element>
            <xsd:element name="USPrice" type="xsd:decimal"/>
            <xsd:element ref="comment" minOccurs="0"/>
            <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
          </xsd:sequence>
          <xsd:attribute name="partNum" type="SKU" use="required"/>
        </xsd:complexType>
      </xsd:element>
    </xsd:sequence>
  </xsd:complexType>
  <!-- Stock Keeping Unit, a code for identifying products -->
  <xsd:simpleType name="SKU">
    <xsd:restriction base="xsd:string">
      <xsd:pattern value="\d{3}-[A-Z]{2}"/>
    </xsd:restriction>
  </xsd:simpleType>
</xsd:schema>