MapR-DB の Container Field Pathについて

本エントリではMapR 6.1.0よりMapR-DBに導入された`Container Field Path`について紹介します。
一言で言うと、`Container Field Path`は配列、ドキュメントといったコンテナ内の値へのアクセスを抽象的に扱うための手法となります。
若干地味めなエントリーになりますが、これを活用することでSecondary Indexをより効率的に使用することが出来ますので、しっかり押さえておきましょう。
なお、内容は[こちらのページ](https://mapr.com/docs/61/MapR-DB/JSON_DB/ContainersOJAIQueryConditions.html#QueryingJSONDocumentFields)をさらっと和訳した感じになりますので、英語でオーケーな方はそちらからどうぞ！

# `Container Field Paths`を使った配列へのアクセス

`Container Field Path`では、単一のスカラ値を一つの要素を持つ配列として扱うことが出来ます。
配列要素もスカラ値はどんなタイプでも扱うことが出来ます。
具体例を以下のテーブルで見てみましょう

```json
{ "_id" : "001", "name" : "Ipod 001", "tags" : [ "electronics", "ipod", "apple" ] }
{ "_id" : "002", "name" : "Ipod 002", "tags" : "ipod" }
{ "_id" : "003", "name" : "Ipod 003", "tags" : 10 }
{ "_id" : "004", "name" : "Ipod 004", "tags" : [ 10, "ipod", { "t" : "ipod" } ] }
{ "_id" : "005", "name" : "Ipod 005", "tags" : { "t" : "ipod" } }
{ "_id" : "006", "name" : "Ipod 006", "tags" : [ { "t" : "ipod" }, { "t" : "apple" } ] }
{ "_id" : "007", "name" : "Ipod 007", "tags" : [ { "t" : "ipod", "v" : 10 }, { "t" : "apple", "v" : 9 } ] }
{ "_id" : "008", "name" : "Ipod 008", "tags" : { "t" : "ipod", "v" : 10 } }
```

こちらのテーブルから`tags[]`と指定して"ipod"を検索してみると、`_id`の001, 002, 004がマッチします。
実際に`dbshell`を使って試してみましょう。

```shell
[mapr@misc2 db]$ hadoop fs -put test.json /tmp/
[mapr@misc2 db]$ mapr importJSON -src /tmp/test.json -dst /tmp/cfp

$ mapr dbshell
====================================================
*                  MapR-DB Shell                   *
* NOTE: This is a shell for JSON table operations. *
====================================================
Version: 6.1.0-mapr

MapR-DB Shell
maprdb mapr:> find /tmp/cfp
{"_id":"001","name":"Ipod 001","tags":["electronics","ipod","apple"]}
{"_id":"002","name":"Ipod 002","tags":"ipod"}
{"_id":"003","name":"Ipod 003","tags":10}
{"_id":"004","name":"Ipod 004","tags":[10,"ipod",{"t":"ipod"}]}
{"_id":"005","name":"Ipod 005","tags":{"t":"ipod"}}
{"_id":"006","name":"Ipod 006","tags":[{"t":"ipod"},{"t":"apple"}]}
{"_id":"007","name":"Ipod 007","tags":[{"t":"ipod","v":10},{"t":"apple","v":9}]}
{"_id":"008","name":"Ipod 008","tags":{"t":"ipod","v":10}}
8 document(s) found.

maprdb mapr:> find /tmp/cfp --q {"$where":{"$eq":{"tags[]":"ipod"}}}
{"_id":"001","name":"Ipod 001","tags":["electronics","ipod","apple"]}
{"_id":"002","name":"Ipod 002","tags":"ipod"}
{"_id":"004","name":"Ipod 004","tags":[10,"ipod",{"t":"ipod"}]}
3 document(s) found.
```

上記のように3つのロウにヒットしました。
この時、001, 004ではtagsが配列であり、"ipod"はこの要素からマッチします
002ではtagsがスカラ値であり、"ipod"はこの値とマッチします

ここに`AND`を混ぜて"ipod"と"apple"で検索してみましょう。

```shell
maprdb mapr:> find /tmp/cfp --c {"$and":[{"$eq":{"tags[]":"ipod"}},{"$eq":{"tags[]":"apple"}}]}
{"_id":"001","name":"Ipod 001","tags":["electronics","ipod","apple"]}
1 document(s) found.
```

この場合は配列であり、かつ、要素に"ipod"と"apple"を持つ001のみヒットしますね

# `Container Field Paths`を使ったドキュメントへのアクセス

ドキュメントのフィールドへの`Container Field Path`の指定も出来ます。
前出の例で `tags[].t` と指定した時にどのような出力になるか試してみましょう。

```shell
maprdb mapr:>  find /tmp/cfp --q {"$where":{"$eq":{"tags[].t":"ipod"}}}
{"_id":"004","name":"Ipod 004","tags":[10,"ipod",{"t":"ipod"}]}
{"_id":"005","name":"Ipod 005","tags":{"t":"ipod"}}
{"_id":"006","name":"Ipod 006","tags":[{"t":"ipod"},{"t":"apple"}]}
{"_id":"007","name":"Ipod 007","tags":[{"t":"ipod","v":10},{"t":"apple","v":9}]}
{"_id":"008","name":"Ipod 008","tags":{"t":"ipod","v":10}}
5 document(s) found.
```

ヒットしたロウの特徴は以下のようになります。

* 005と008は単一のドキュメントとしてヒットします
* 006と007はドキュメントの配列としてヒットします
* 004はスカラ値もドキュメントも持つ配列の中でヒットします


# `Container Field Paths`を使ったキーの存在確認

今までは`$eq`を使ったvalueのマッチでしたが、`$exists`を使ってキーが存在するかどうかをチェックすることも出来ます。
例えば`{"$exists":"tags[].t"}`とすることで、`tags`という名前の配列もしくは単一ドキュメントに"t"というキーが存在するを確認できます。

```shell
maprdb mapr:> find /tmp/cfp --q {"$where":{"$exists":"tags[].t"}}
{"_id":"004","name":"Ipod 004","tags":[10,"ipod",{"t":"ipod"}]}
{"_id":"005","name":"Ipod 005","tags":{"t":"ipod"}}
{"_id":"006","name":"Ipod 006","tags":[{"t":"ipod"},{"t":"apple"}]}
{"_id":"007","name":"Ipod 007","tags":[{"t":"ipod","v":10},{"t":"apple","v":9}]}
{"_id":"008","name":"Ipod 008","tags":{"t":"ipod","v":10}}
5 document(s) found.
```

反対に`$notexists`を使うことで、存在しないロウを検出できます。

```shell
maprdb mapr:> find /tmp/cfp --q {"$where":{"$notexists":"tags[].t"}}
{"_id":"001","name":"Ipod 001","tags":["electronics","ipod","apple"]}
{"_id":"002","name":"Ipod 002","tags":"ipod"}
{"_id":"003","name":"Ipod 003","tags":10}
{"_id":"004","name":"Ipod 004","tags":[10,"ipod",{"t":"ipod"}]}
4 document(s) found.
```

ここで004がヒットすることが少し不思議な感じですが、`$notexists`では、**10**,**ipod**と存在しない条件に引っかかった場合でもヒットします。
そのため、004は`$exists`でも`$notexists`でもヒットする形になります。

# `Container Field Paths`を使った複数レベルにネストされたドキュメントと配列へのアクセス

これまでの条件を組み合わせて以下のようなクエリを作ることも出来ます。

```json
{"$eq":{"projects[].customer.contacts[].emails[].value":"jdoe@gmail.com"}}
{"$eq":{"projects[].customer.contacts[].role":"CEO"}}
```

実際に以下のようなテーブルがあった場合ヒットします

```json
{
   "_id": "account001",
   "projects": [
      {
         "id": "proj001",
         "manager": { "name": "Guy Bones", "email": "gbones@pro.com" },
         "customer": {
            "name": "My Company",
            "contacts": [
               {
               "id": "user_jdoe",
               "emails": [
                  { "type": "work", "value": "jdoe@comp.com" },
                  { "type": "personal", "value": "jdoe@gmail.com" }
               ],
               "addresses": [
                  {
                     "type": "work",
                     "value": {"street":"21 King Av","city": "Redwood", "zip": 94065, "state": "CA" } 
                  }
               ],
               "phones": [
                  { "type": "cell", "value": "+16505556764" },
                  { "type": "office", "value": "+14075556764" }],
               "role": "CEO"
               }, 
               {
               "id": "user_simsom",
               "emails": [
                  { "type": "work", "value": "simson@comp.com" }, 
                  { "type": "personal", "value": "simson@gmail.com" }
               ],
               "addresses": [
                  { 
                  "type": "work", 
                  "value": {"street":"21 King Av.","city":"Redwood","zip": 94065,"state": "CA" } 
                  }
               ],
               "phones": [
                  { "type": "cell", "value": "+16505556777" }, 
                  { "type": "office", "value": "+1407555444" }],
               "role": "PM"
               }
            ]
         }
      },
      {
         "id": "proj002",
         // ...
      }
   ]
}
```


# `Container Field Paths`を使った多次元配列へのアクセス

最後に多次元配列でのケースを見てみましょう。
下のjsonは毎日の最高/最低気温を週ごとの`_id`としてまとめたものになります。
気温の`temps`は`[最高気温、最低気温]`というシンタックスで登録されます。
データが手に入らない場合(`002`)を除けば、`temps`は7日分のデータが登録されます。

```json
{
   "_id" : "001",
   "temps" : [[61,49],[74,51],[75,51],[74,52],[78,54],[75,53],[75,54]],
   "weekOf" : "4/29/2018"
}
{
   "_id" : "002",
   "temps" : [81,60],
   "weekOf" : "5/12/2018"
}
{
   "_id" : "003",
   "temps" : [[80,55],[78,54],[79,54],[77,53],[79,54],[77,54],[78,54]],
   "weekOf" : "5/13/2018"
}
```

この時、以下のように後続の配列でインデックスを指定していた場合、先行の配列では`Container Field Path`を使うことが出来ません。

```json
{"$ge":{"temps[][1]":60}}
```

実際に利用可能なオプションは、`{"$ge":{"temps[][]":60}}`, `{"$ge":{"temps[1][]":75}}`, `{"$eq":{"temps[]":[78,54]}}` といった形式になります。
実際に確認してみましょう。

```shell
maprdb mapr:> find /tmp/temperature --q {"$where":{"$ge":{"temps[][]":60}}}
{"_id":"001","temps":[[61,49],[74,51],[75,51],[74,52],[78,54],[75,53],[75,54]],"weekOf":"4/29/2018"}
{"_id":"002","temps":[81,60],"weekOf":"5/12/2018"}
{"_id":"003","temps":[[80,55],[78,54],[79,54],[77,53],[79,54],[77,54],[78,54]],"weekOf":"5/13/2018"}
3 document(s) found.

maprdb mapr:> find /tmp/temperature --q {"$ge":{"temps[][]":60}}
Invalid Query Operator: "$ge"
maprdb mapr:> find /tmp/temperature --q {"$where":{"$ge":{"temps[1][]":75}}}
{"_id":"003","temps":[[80,55],[78,54],[79,54],[77,53],[79,54],[77,54],[78,54]],"weekOf":"5/13/2018"}
1 document(s) found.

maprdb mapr:> find /tmp/temperature --q {"$where":{"$eq":{"temps[]":[78,54]}}}
{"_id":"001","temps":[[61,49],[74,51],[75,51],[74,52],[78,54],[75,53],[75,54]],"weekOf":"4/29/2018"}
{"_id":"003","temps":[[80,55],[78,54],[79,54],[77,53],[79,54],[77,54],[78,54]],"weekOf":"5/13/2018"}
2 document(s) found.
```

# まとめ


本エントリではMapR-DB JSONのOJAI APIの機能である`Container Field Path`について紹介しました。
Secondary Indexを活用する上では効果的にインデックスを張る必要がありますが、この際に`Container Field Path`を知っておくことが非常に重要となります。
こちらについてはまた後日のエントリーにて紹介する予定です。