レビュー: JSONドキュメント・コンテンツのセカンダリ索引

全文検索のために、Oracle NoSQL Database表に格納されているJSONドキュメントのコンテンツに索引付けする方法は、次の項で示します。ただし、この項の内容をより深く理解するために、SQLリファレンス・ガイドJSONの索引付けの内容を最初に確認する必要があります。ここでは、値が有効なJSON形式の文字列で構成されている場合(つまり、値がJSONドキュメントの場合)に、NoSQL表のフィールドに値を格納する方法について説明しています。

これらの内容を確認するときは、JSONコンテンツでのセカンダリ索引の作成とテキスト索引の作成を混同しないことが重要です。JSONドキュメントを含むフィールドでのテキスト索引の作成の詳細は、このドキュメントの次の項を参照してください。

JSONがOracle NoSQL Database表に格納されている場合、データは任意の有効なJSONで、JSONドキュメントと呼ばれる文字列として格納できます。NoSQL表のフィールド(列)に格納されるこのようなドキュメントはそれぞれ、ドキュメントのフィールドまたは属性と呼ばれる要素で構成されます。したがって、次の項で特定のJSONドキュメントの要素について説明するときは、フィールドという用語と属性という用語は同じ意味で使用できます。コンテキストで、Oracle NoSQL表のフィールド(列)と、表に格納されているJSONドキュメントのフィールド(属性)を区別する必要があります。

特定の表に格納されたJSONドキュメントの属性に対してセカンダリ索引を作成できますが、そのような索引には多くの制限があります。これは、テキスト索引をより魅力的にする制限です。まず、セカンダリ索引を作成する場合、ドキュメントのスカラー属性のみに索引付けできます。つまり、属性はネストしたJSONオブジェクトにはできません。また、JSONセカンダリ索引のサポートされるOracle NoSQLのデータ型は、integerlongdoublenumberstringおよびbooleanのみです。最後に、このような索引に対しては、全文検索は実行できません。

たとえば、次に示すJSONドキュメントには、コンテンツが米国上院の特定の議員に関連する情報が示されています。上院議員(現在と過去の両方)ごとに、ここに示すようなJSONドキュメントが作成され、このような各ドキュメントを特定の表の列に格納するためにOracle NoSQL表APIを使用できます。この項全体およびその次の項では、セカンダリ索引またはテキスト索引で、このようなJSONドキュメントに索引付けする方法を示すために、ここに示すJSONドキュメントの例が何度も参照されています。

{
  "description": "Senior Senator for Ohio", 
  "party": "Democrat",
  "congress_numbers": [223,224,225], 
  "state": "OH",
  "startdate": "2010-01—03T05:04:09.456",
  "enddate": "2020-11-12T03:01:02.567812359",
  "seniority": 37, 
  "current": true,
  "duties": {
    "committee": ["Ways and Means","Judiciary","Steering"],
    "caucus": ["Automotive","Human Rights","SteelIndustry"]
  },
  "personal": {
    "firstname":"Sherrod",
    "lastname":"Brown",
    "birthday":"1952-11-09",
    "social_media": {
      "website":"https://www.brown.senate.gov",
      "rss_url":"http://www.brown.senate.gov/rss/feeds",
      "twittered":"SenSherrodBrown"
    },
    "address": {
      "home": {
        "number":"9115-ext",
        "street":"Vaughan",
        "apt":null,
        "city":"Columbus",
        "state":"OH",
        "zipcode":43221,
        "phone": "614-742-8331"
      },
      "work": {
        "number":"Hart Senate Office Building",
        "street":"Second Street NE",
        "apt":713,
        "city":"Washington",
        "state":"DC",
        "zipcode":20001
        "phone": "202-553-5132"
      }
    }, 
    "cspanid": 57884
  }, 
  "contrib": 2571354.93
}

前述のJSONドキュメントの例は、様々な型の様々なJSON属性で構成されています。一部の属性は、"name":"value"形式のスカラー・フィールドですが、その他はネストしたオブジェクト、またはスカラー値の配列です。ネストしたオブジェクトである属性は、中カッコ{...}でカプセル化された構造となり、有効な一連のJSONフィールド型、つまり、スカラー、スカラーの配列およびJSONオブジェクト(名前付きまたは名前なし)の任意の組合せが含まれます。配列型は、順序付けられた要素のカンマ区切りリストとなり、大カッコ[...]でカプセル化されます。各要素は、同じスカラー型で、stringdateまたは数値型(integerdoublenumberなど)である必要があります。

オブジェクト内にネストされたスカラー・フィールドの値は、JSONパス表記を使用して間接参照されます。たとえば、それぞれの上院議員の生年月日が含まれるスカラー・フィールドは、personalという名前のオブジェクトにネストされます。そのため、各上院議員の生年月日は、JSONパスjsonFieldName.personal.birthdayを使用して検索問合せに指定できます。ここで、jsonFieldNameコンポーネントの値は、各JSONドキュメントが書き込まれる表の列に指定された名前です。同様に、各上院議員の出身地についての検索は、jsonFieldName.personal.address.home.cityというパスを使用して表現できます。

Elasticsearchでは、配列フィールドは予期しない方法で処理されることに注意してください。Elasticsearch内で配列を問い合せるときは、最初の要素、最後の要素、索引3の要素などを参照できません。配列は、同じ型の値のバッグとして処理されます。前述のドキュメントの例で、各上院議員が委員を務める委員会を検索する場合は、jsonFieldName.duties.committee[0]などのパスを使用して問合せを構成しません。このようなパスは許可されていません。かわりに、配列の要素となる検索対象の値とともに、配列自体のパスを指定します。たとえば、"jsonFieldName.duties.committee":"Judiciary Steering"のように指定します。

前述のように、JSONドキュメントの各属性には型があります。属性の構造または属性に関連付けられた値によって型が暗黙的に表されます。コンテンツが中カッコで囲まれてカプセル化されているJSONドキュメントの属性は、その属性がJSONオブジェクト型であることを暗黙的に示しています。スカラー・フィールドに関しては、そのようなフィールドに関連付けられた値の暗黙的なデータ型は、フィールド自体の値に依存します。このことは、索引がセカンダリ索引であるかテキスト索引であるかに関係なく当てはまります。たとえば、前述のJSONドキュメントのdescriptionおよびseniorityという名前のスカラー属性は、それぞれstring型およびinteger型として処理されます。

これをJSONドキュメントのcontrib属性(2571354.93)に指定された値などと比較します。セカンダリ索引を作成するときには、このようなスカラー値はNoSQL doubleデータ型として処理されます。Elasticsearchクラスタで全文検索のテキスト索引を作成する場合は、Elasticsearchのfloat型またはdouble型として処理されます。同様に、日時を表す情報を含む属性(startdateenddatebirthdayなどの属性)については、このようなフィールドの値は、セカンダリ索引の作成時には、Oracle NoSQL string型としてのみ処理できますが、テキスト索引の作成時には、Elasticsearchのstring型またはdate型として処理できます。

最後に、大カッコでカプセル化されたスカラーのカンマ区切りリストを含む属性は、JSON配列型を暗黙的に示しますが、配列の要素のデータ型(つまり、配列の型)は、スカラー属性について前述した方法と同じ方法で要素の値で暗黙的に示されます。

次に、表の主キーを含むidフィールドと、前述のドキュメントの例のようなJSONドキュメントで構成される値を含むjsonFieldという名前のフィールドで構成される、jsonTableという名前の表を作成するとします。そのような表を作成し、その結果の構造を調べるには、管理CLIから次のようなコマンドを実行します。

kv-> execute 'CREATE TABLE jsonTable
         (id INTEGER, jsonField JSON, PRIMARY KEY (id))';

kv-> execute 'DESCRIBE AS JSON TABLE jsonTable';
{
  "json_version" " 1,
  "type" : "table",
  "name" : "jsonTable",
  "shardKey" : [ "id" ],
  "primaryKey" : [ "id" ],
  "fields" : [ {
    "name" : "id",
    "type" : "INTEGER",
    "nullable" : false,
    "default" : null
  }, {
    "name": "jsonField",
    "type" : "JSON",
    "nullable" : true,
    "default" : null
  } ]
}

前述のドキュメントの例のようなJSONドキュメントを表に移入するには、次のようなコードを実行します。

final KVStore store = KVStoreFactory.getStore
   (new KVStoreConfig(<storeName>, <host> + ":" + <port>));
final tableAPI = store.getTableAPI();
final table = tableAPI.getTable("tsTable");
final List<String> listOfJsonDocs = {...}; 
for (int i = 0; i < listOfJsonDocs.size(); i++) {
    final Row row = table.createRow();
    row.put(id, i);
    row.putJson("jsonField", listOfJsonDocs.get(i));
    tableAPI.putIfAbsent(row, null, null); 
}

(表APIのメソッドrow.putJsonを使用して)表に必要なJSONドキュメントを移入した後で、表のjsonFieldフィールドに格納されている各ドキュメントの選択した属性のセカンダリ索引を作成するには、次のようなコマンドを実行します。

kv-> execute 'CREATE INDEX jsonSecIndex ON jsonTable
         (jsonField.party AS STRING,
          jsonField.current AS BOOLEAN,
          jsonField.contrib AS DOUBLE,
          jsonField.seniority AS INTEGER)';

この場合、問合せは、各上院議員の政党所属、年功、上院議員の選挙活動に対する献金の合計金額、および上院議員が現在現職の上院議員であるかどうかの様々な組合せに基づいて実行できます。たとえば、献金の合計が100万から2000万ドルである現在の民主党上院議員をすべて検索するには、管理CLIから次のようなコマンドを実行します。

kv-> GET TABLE –name jsonTable 
  –index jsonSecIndex 
  -field jsonField.party –value "Democrat" 
  -field jsonField.current –value true 
  -field jsonField.contrib –start 1000000.00 –end 20000000