本エントリではMapR 6.1.0よりMapR-DBに導入されたContainer Field Path
について紹介します。
一言で言うと、Container Field Path
は配列、ドキュメントといったコンテナ内の値へのアクセスを抽象的に扱うための手法となります。
若干地味めなエントリーになりますが、これを活用することでSecondary Indexをより効率的に使用することが出来ますので、しっかり押さえておきましょう。
なお、内容はこちらのページをさらっと和訳した感じになりますので、英語でオーケーな方はそちらからどうぞ!
Container Field Paths
を使った配列へのアクセス
Container Field Path
では、単一のスカラ値を一つの要素を持つ配列として扱うことが出来ます。
配列要素もスカラ値はどんなタイプでも扱うことが出来ます。
具体例を以下のテーブルで見てみましょう
{ "_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
を使って試してみましょう。
[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"で検索してみましょう。
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
と指定した時にどのような出力になるか試してみましょう。
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"というキーが存在するを確認できます。
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
を使うことで、存在しないロウを検出できます。
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
を使った複数レベルにネストされたドキュメントと配列へのアクセス
これまでの条件を組み合わせて以下のようなクエリを作ることも出来ます。
{"$eq":{"projects[].customer.contacts[].emails[].value":"jdoe@gmail.com"}}
{"$eq":{"projects[].customer.contacts[].role":"CEO"}}
実際に以下のようなテーブルがあった場合ヒットします
{
"_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日分のデータが登録されます。
{
"_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
を使うことが出来ません。
{"$ge":{"temps[][1]":60}}
実際に利用可能なオプションは、{"$ge":{"temps[][]":60}}
, {"$ge":{"temps[1][]":75}}
, {"$eq":{"temps[]":[78,54]}}
といった形式になります。
実際に確認してみましょう。
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
を知っておくことが非常に重要となります。
こちらについてはまた後日のエントリーにて紹介する予定です。