0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MapRAdvent Calendar 2017

Day 18

MapR-DB の Container Field Pathについて

Last updated at Posted at 2019-04-16

本エントリでは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を知っておくことが非常に重要となります。
こちらについてはまた後日のエントリーにて紹介する予定です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?