CloudantDBとSQL系DBの違い
- SQL系:
一つのDBの中に複数のテーブルがありえる。テーブルの中にデータがある。テーブルにはスキーマが必須なのでデータに型がある。 検索するにはSQL Queryで書く -
CloudantDB:
一つのDBの中に沢山のJSONドキュメントがある。スキーマがない。それぞれのドキュメントにはJSONである以外の制約がないからドキュメントごと全く異なる情報を持つことができる。 ドキュメントIDで検索することができるがsqlクエリみたいなものを実現するにはsearch indexを事前に作る必要がある。クエリーはCloudant Queryといい Lucene Queryベースだそうである。
登録
まず登録しアカウント名とパスワードを取得し試しに幾つかのDBを作る。簡単なAPIを使う
簡単の為にターミナルでcurlを使う。そして、ユーザー認証が必要だから次の変数を定義する。(本当はcurlの-u
オプションで自分のパスワードを入力した方がbashのhistoryに残らなくてセキュリティ的にいいけどここでは最もシンプルな方法で紹介することにする)
CLOUDANT='https://アカウント名:パスワード@アカウント名.cloudant.com'
- 😵 curlの復習:
curl <url> \ -X <method> \ -H <header> \ -d <data>
*メソッドを指定しない場合 通常はGETとなる。
DB一覧を取得
これは一番簡単なAPIで上記のアカウント名とパスワードを試せる。curl ${CLOUDANT}/_all_dbs
["_users","big","myanswers"]
DBを作成
car_answers
というDBを用意する。
Cloudantのコンソルを使って5つのドキュメントを作ることができるが、ここでAPIで行うcurl ${CLOUDANT}/car_answers -X PUT
{"ok":true}
複数のドキュメントをアップロード
上記で作成したDBに次の5つのドキュメントをアップロードする。バルクというAPIを使うために一つのオブジェクトにdocs
というキーにドキュメントの配列を定義する必要がある。ここでは_id
を自分で指定しているけれども これは後で検索するから見やすさのためだけである。指定しなくてよい。Cloudantがユニークなものを生成してくれて それらをリスポンスに返ってくる。
json='{ "docs": [ { "_id": "1", "created_at": "2015-05-15 00:00:00", "maker": "Mercedes Benz", "モデル": "A 180", "answers": { "評価": "★★★", "その他": "特になし" } }, { "_id": "2", "created_at": "2015-05-16 00:00:00", "maker": "Toyota", "モデル": "レクサス", "answers": { "評価":"★★★★", "その他":"よかった" } }, { "_id": "3", "created_at": "2015-05-17 00:00:00", "maker": "Honda", "モデル": "フィット", "answers": { "評価":"★★★★★", "その他":"すぐ購入したいと思います" } }, { "_id": "4", "created_at": "2015-05-18 00:00:00", "maker": "Mercedes Benz", "モデル": "E 250 CABRIOLET", "answers": { "評価":"★★", "その他":"イメージしていたものと少し違った" } }, { "_id": "5", "other": "他とちがうもの" } ] }'そして、この大きなオブジェクトをリクエストのボディーに入れて送信
curl ${CLOUDANT}/car_answers/_bulk_docs -X POST -H "Content-Type: application/json" -d "$json"
[{"id":"1","rev":"1-a408ef0c09d5b0aaec96b87e04e049de"},{"id":"2","rev":"1-615734de2db3b4717544f2dfd0e01bec"},{"id":"3","rev":"1-fd7d17addf8149d0c522368e69bda27c"},{"id":"4","rev":"1-d0be948351c84706c5febea1a1622700"},{"id":"5","rev":"1-4d8829cf3843c4d7a0b623bcced3a620"}]
単品でドキュメントをアップロード
バルクではなく、単品でドキュメントをアップロードもちろん可能。バルクと同じくid
やrev
を指定しなくてよくてCloudantはユニークなものを生成してくれて 生成されたid
とrev
番号がリスポンスに返ってくる。
json='{ "created_at": "2016-07-14 00:00:00", "maker": "Android", "モデル": "N", "answers": { "評価": "★★★★★", "その他": "特になし" } }' curl ${CLOUDANT}/car_answers/ -X POST -H "Content-Type: application/json" -d "$json"
{"ok":true,"id":"c4d9e99028d5b4016ef008768bfee9a6","rev":"1-8e040f92ceea84466d2a90b5507f93b1"}
ドキュメント一覧を取得
例えば、car_answers
というDBにある全ドキュメントを取得するには_all_docs
を使って取れる
curl ${CLOUDANT}/car_answers/_all_docs
{"total_rows":5,"offset":0,"rows":[ {"id":"1","key":"1","value":{"rev":"1-a408ef0c09d5b0aaec96b87e04e049de"}}, {"id":"2","key":"2","value":{"rev":"1-615734de2db3b4717544f2dfd0e01bec"}}, {"id":"3","key":"3","value":{"rev":"1-fd7d17addf8149d0c522368e69bda27c"}}, {"id":"4","key":"4","value":{"rev":"1-d0be948351c84706c5febea1a1622700"}}, {"id":"5","key":"5","value":{"rev":"1-4d8829cf3843c4d7a0b623bcced3a620"}} ]}デフォルトではリスポンスの
rows
にドキュメントのid
だけ渡される。ドキュメントその物を含んで欲しいときはinclude_docs
フラグが便利
curl ${CLOUDANT}/car_answers/_all_docs?include_docs=trueデフォルトでは一つのリクエストで最大取得可能なドキュメントの数は25だが、それを
limit
で200まで変更可
curl ${CLOUDANT}/car_answers/_all_docs?include_docs=true&limit=200ドキュメント取得の詳細:docs.cloudant.com/database.html#get-documents
特定なドキュメントを取得
ドキュメントのid
がわかれば そのドキュメントを簡単に取得できる。例えば、car_answers
のid
が3のドキュメントを取る:
curl ${CLOUDANT}/car_answers/3
{"_id":"3","_rev":"1-fd7d17addf8149d0c522368e69bda27c","created_at":"2015-05-17 00:00:00","maker":"Honda","モデル":"フィット","answers":{"評価":"★★★★★","その他":"すぐ購入したいと思います"}}条件を指定してドキュメントの取得は次に説明する。
ドキュメントを条件を指定して検索
CloudantのDBを検索するには検索インデックス(略してインデックス)が必要。インデックスについて
インデックスとは
条件を指定して検索を実現するのは検索インデックスである。DBに入っているドキュメントはどんなものかCloudantにはわからない、まして、全ドキュメントは同じフォーマットである保証もない。キーワードで検索したいときに ドキュメントのどのフィルドをどのように扱えばいいかを決めるのは検索インデックスである。検索インデックスは必ずデザインドキュメントの中にあり、デザインドキュメントに複数の検索インデックスが存在しえる。インデックスの作成
他のドキュメントと同じくAPIで作成可能だが 今回はCloudantDBのコンソルで作成する。car_answers
というDBを選択しAll Design Docsの➕ボタンを選択しNew Search Indexを選ぶ。そしてCreate Search Indexという画面が表示される。Create Search Indexの入力フィルドの説明
-
Save to Design Document
どこのデザインドキュメントに保存するかを選択できるプルダウンメニューがあるが、一個もないときにデザインドキュメント作成用の入力フィルドも表示される。全てのデザインドキュメントが"_design/"で始まるのでそれは固定となっている -
Index name
検索インデックスの名前 -
Search index function
これはインデックス化するに使う関数。入力としてはドキュメントが渡される。出力はない。インデックス化したいドキュメントのパラメーターをindex(...)
でインデックスに入れることが役割。極普通のjavascriptの関数。使い方についてはこのあと説明する。 -
Analyzer
インデックスに入れたテキスト(この場合はdoc.maker
)をどういう風に区切って扱うかを決めるのはアナライザーだ。多分Tokenizerという言い方をする方がすぐわかりやすい
例えばドキュメント1を処理した場合は"Mercedes Benz"
というストリングがインデックスされる。"Mercedes Benz"
を一つのストリングとして扱う("Keyword")か英語の文法を使って単語で分けるか日本語の文法で分けるかUnicode Text Segmentationアルゴリズム("Standard")で分けるかなど。アナライザーによって検索でドキュメントがヒットするかしないか変わる。より詳細は[Cloudant/For Developers/Search Indexes/Analyzers]を参照。
関数ごとに一個のアナライザーを使う場合はSingle。インデックス化されるパラメーターごとにアナライザーを使う場合はMultipleを選択。
ドキュメントの検索・初級編
Analyzerはstandardでsingleな次のインデックスを作る(上記の画像と同じインデックス)// インデックス名:car function (doc) { index("brand", doc.maker); }
curl ${CLOUDANT}/car_answers/_design/search/_search/car?q=brand:Honda
{"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI4LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAmJgAAAAYn____9qaAJGP9OjegAAAABhAGo","rows":[{"id":"3","order":[0.3068528175354004,0],"fields":{}}]}
URL部 | 意味 |
---|---|
car_answers | DBの名前 |
_design/search | デザインドキュメントのID |
_search | 検索時に使う決まり |
car | 検索インデックスの名前 |
q | CloudantQuery用のパラメーター |
brand:"Honda" | CloudantQueryそのもの |
他に試してみる
curl ${CLOUDANT}/car_answers/_design/search/_search/car?q=brand:Mercedes%20Benz
{"total_rows":2,"bookmark":"g1AAAACOeJzLYWBgYMpgTmGQS0lKzi9KdUhJMtXLTMo1MLDUS87JL01JzCvRy0styQGpy2MBkgwNQOr____zszKY3OznnJMDiSUyoJphQcCMBxAz_qOakQUAaqAqIg","rows":[{"id":"1","order":[0.028130024671554565,0],"fields":{}},{"id":"4","order":[0.028130024671554565,0],"fields":{}}]}見つかった!ドキュメント1と4が返ってきている!
_search
でもinclude_docs
オプションも使えるしlimit
も使える。しかしbashなので&
などをエンコードしないといけないしその次にURLエンコードしないといけないしまだ使っていないけどCloudantQueryの中でエスケープしないといけないものもあるのでこれから-v
オプションを使う。実際に行われたリクエストがわかるから。
curl -v ${CLOUDANT}/car_answers/_design/search/_search/car?q=brand:Mercedes%20Benz\&limit=1\&include_docs=true
> GET /car_answers/_design/search/_search/car?q=brand:Mercedes%20Benz&limit=1&include_docs=true HTTP/1.1 ... {"total_rows":2,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI1LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAm4EAAAAAIBuBAD___-famgCRj-czh4AAAAAYQBq","rows":[{"id":"1","order":[0.028130024671554565,0],"fields":{},"doc":{"_id":"1","_rev":"1-a408ef0c09d5b0aaec96b87e04e049de","created_at":"2015-05-15 00:00:00","maker":"Mercedes Benz","モデル":"A 180","answers":{"評価":"★★★","その他":"特になし"}}}]}定義された検索インデックス関数ではこれ以上大したものができないのでもうすこし拡張していく。
ドキュメントを検索・中級編
同じデザインドキュメントの中で新しい検索インデックスcar2
を作成。同じくAnalyzerをstandard
にする。
// インデックス名:car2 function (doc) { if (typeof doc.maker === 'undefined') { // makerというフィルドをもっていないドキュメントを相手にしない. //(なんだかの理由でmakerをもっているもっていないドキュメントがある) return; } if (doc.created_at) { // 日付で検索したい。例: q=created_at:2015-09-25 11:30:58 ただし日付の部分をエスケープが必要 index("created_at", doc.created_at, {"store": true}); // 日付で楽に検索したい。例: q=20150925113058 index("created", parseInt(doc.created_at.replace(/[^0-9]/g, ''), 10), {"store": true}); } if (doc["モデル"]) { // 日本語キーで検索は可能だがエスケープが必要。これで楽にできる。「モデル」の代わりに「model」で検索可 index("model", doc["モデル"], {"store": true}); } if (doc.answers && doc.answers["評価"]) { // 評価で検索したい。例:q=stars:★★★ ただし★をエスケープしなければならない var stars = doc.answers["評価"]; index("stars", stars, {"store": true}); // 評価で楽に検索したい。例:q=stars_num:3 index("stars_num", (stars.match(/★/g) || []).length); } if (doc.answers["その他"]) { // コメントなどで検索したい index("comment", doc.answers["その他"], {"store": true, "index": false}); } }
注意点
Javascriptの標準の挙動だが、関数のどっかでExceptionが発生するとそれ以降は実行されなくて インデックス化されないので 検索でヒットしないことがある。関数のよくチェックすること。
index関数の第三パラメーターについて
詳細は[Cloudant/For Developers/Search Indexes/Options]に書かれてあるけど、まとめると次のようになるキー | 意味 | デフォルト |
---|---|---|
store | 検索結果に値を含むかどうか。 | false |
index | 検索インデックスに入れるかどうか。 | true |
facet | faceting機能をオンにするかどうか。今回は使わない | false |
boost | 検索結果の中で優先度を上げる係数。今回は使わない | 1.0 |
いろいろと検索してみる
まずは({"store": true}
を付けた)モデルで検索すると
curl -v ${CLOUDANT}/car_answers/_design/search/_search/car2?q=model:CABRIOLET
> GET /car_answers/_design/search/_search/car2?q=model:CABRIOLET HTTP/1.1 ... {"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI4LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAm4EAAAAAOBuBAD_____amgCRj_Do3oAAAAAYQBq","rows":[{"id":"4","order":[0.1534264087677002,0],"fields":{"stars":"★★","model":"E 250 CABRIOLET","comment":"イメージしていたものと少し違った","created_at":"2015-05-18 00:00:00"}}]}今までと違ってより詳細な情報を取得していることがわかる。それは
store:true
のお陰。これでinclude_docs
を使わずに欲しい情報だけを取ることができる。
CloudantDBのコンソルで インデックスを簡単に試すことができる。URL全部書く必要がなくて Cloudant Queryの部分のみ、いろいろ試すには便利だねおなじく
エスケープについては便利かどうかを試したい。
curl ${CLOUDANT}/car_answers/_design/search/_search/car2?q=stars_num:2
{"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI3LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAm4EAAAAAOBuBAD_____amgCRj_wAAAAAAAAYQBq","rows":[{"id":"4","order":[1.0,0],"fields":{"stars":"★★","model":"E 250 CABRIOLET","comment":"イメージしていたものと少し違った","created_at":"2015-05-18 00:00:00","created":20150518000000.0}}]}
Official docsメモ
The following characters require escaping if you want to search on them;+ - && || ! ( ) { } [ ] ^ " ~ * ? : \ /Escape these with a preceding backslash character. https://cloudant.com/for-developers/search/
日本語で検索
ここからエスケープがかなりわけがわからなくなるのでcurlの--data-urlencode
オプションでクエリエンコーディングをする。そして-G
オプションで urlに付ける。さらに-v
でverboseをオンにして実際に叩かれたURLの確認ができる。
model:"フィット"
の検索はこんな感じ
curl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=model:"フィット"'
> GET /car_answers/_design/search/_search/car2?q=model%3A%22%E3%83%95%E3%82%A3%E3%83%83%E3%83%88%22 HTTP/1.1 ... {"total_rows":1,"bookmark":"g2wAAAABaANkAB5kYmNvcmVAZGI3LmlibTAwOS5jbG91ZGFudC5uZXRsAAAAAmJgAAAAYn____9qaAJGP9OjegAAAABhAGo","rows":[{"id":"3","order":[0.3068528175354004,0],"fields":{"stars":"★★★★★","model":"フィット","comment":"すぐ購入したいと思います","created_at2":"2015-05-1700:00:00","created_at":"2015-05-17 00:00:00","created":20150517000000.0}}]}
stars:"★★"
の検索は次の通りのはずだがうまくいかない。
curl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=stars:"★★"'
> GET /car_answers/_design/search/_search/car2?q=stars%3A%22%E2%98%85%E2%98%85%22 HTTP/1.1 {"total_rows":0,"bookmark":"g2o","rows":[]}その理由はAnalyserの
standard
にある。AnalyzerについてCloudant Analizersにの述べているように standard
で"★★"というストリングをトークン化しようとするとトークンの数がゼロなのでstandard
で記号を決してヒットしない。別のインデックスを作っておく必要がある。この場合はkeyword
もしくはwhitespace
がいいかと思う。
レンジ検索・数字編
数字は割りと簡単で問題なしcurl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=created:[20150501000000 TO 20160705235959]'
> GET /car_answers/_design/search/_search/car2?q=created%3A%5B20150501000000%20TO%2020160705235959%5D HTTP/1.1 {"total_rows":4,"bookmark":"g1AAAAELeJzLYWBgYMlgTmGQS0lKzi9KdUhJMtHLTMo1MLDUS87JL01JzCvRy0styQGqY0pkSJL___9_VgaTm_0HBjBIZEDVbIZTc1ICkEyqJ6Aft-V5LCD1DUAKaMR88twAMeMBxAw0d2QBADHUTsY","rows":[{"id":"2","order":[1.0,0],"fields":{"stars":"★★★★","model":"レクサス","stars2":4.0,"comment":"よかった","created_at":"2015-05-16 00:00:00","created":20150516000000.0}},{"id":"3","order":[1.0,0],"fields":{"stars":"★★★★★","model":"フィット","stars2":5.0,"comment":"すぐ購入したいと思います","created_at":"2015-05-17 00:00:00","created":20150517000000.0}},{"id":"1","order":[1.0,0],"fields":{"stars":"★★★","model":"A 180","stars2":3.0,"comment":"特になし","created_at":"2015-05-15 00:00:00","created":20150515000000.0}},{"id":"4","order":[1.0,0],"fields":{"stars":"★★","model":"E 250 CABRIOLET","stars2":2.0,"comment":"イメージしていたものと少し違った","created_at":"2015-05-18 00:00:00","created":20150518000000.0}}]}
レンジ検索・ストリング編
この辺になると またエスケープがややこしすぎてわけわからなすぎる。検索したいのは2015-05-01 00:00:00
ストリングなので"
で囲む。内容の:
と-
をエスケープが必要。しかし 上記と同じレンジを指定しているのに結果が違う。結果が違う理由はAnalizerの
standard
によってトークン化された結果が違うからだ。数字をトークン化しようとすると同じ数字になるけど、数字でない場合はスペースやさまざまな記号で別れたりする。この場合は完全マッチの検索に近いから
keyword
かwhitespace
がいいかもしれないね。AnalyzerについてCloudant Analizersに書いた。
とりあえず有効なqueryのようだが 期待しているものではない
curl -v -G ${CLOUDANT}/car_answers/_design/search/_search/car2 --data-urlencode 'q=created_at:["2015\-05\-01 00\:00\:00" TO "2016\-07\-05 23\:59\:59"]'
> GET /car_answers/_design/search/_search/car2?q=created_at%3A%5B%222015%5C-05%5C-01%2000%5C%3A00%5C%3A00%22%20TO%20%222016%5C-07%5C-05%2023%5C%3A59%5C%3A59%22%5D HTTP/1.1 {"total_rows":2,"bookmark":"g1AAAACPeJzLYWBgYMpgTmGQT0lKzi9KdUhJMjTRy0zKNTCw1EvOyS9NScwr0ctLLckBKcxjAZIMC4DU____92dlMLnZf2AAg0QGkCFycEPMCZjxAGLGfxQzGLMAarcpYw","rows":[{"id":"7fbfa0bbc27b8ab664d64cde5d00b421","order":[1.0,0],"fields":{"stars":"★★★★★","model":"N","stars2":5.0,"comment":"特になし","created_at":"2016-07-14 00:00:00","created":20160714000000.0}},{"id":"c4d9e99028d5b4016ef008768bfee9a6","order":[1.0,1],"fields":{"stars":"★★★★★","model":"N","stars2":5.0,"comment":"特になし","created_at":"2016-07-14 00:00:00","created":20160714000000.0}}]}
ドキュメントの検索・上中級(TODO)
* Multiple Analizersを持つインデックスでの検索("★"などの記号で検索したい)* 検索:
index:false
などについて(検索キーワードとして使いたくないけど(store:trueに合わせて使うと)と結果に出て欲しいとき)* 検索: ranges, [], (), AND, OR, などについて
CloudantQueryでエスケープについて
StackoverflowのEscaping Lucene Characters using Javascript Regexの回答で大抵カバーできるけど、下記では\と/とも対応している。function luceneEscaped(input) { // http://stackoverflow.com/questions/26431958/ + own additions // + - && || ! ( ) { } [ ] ^ " ~ * ? : \ / input = input.replace(/([\!\*\+\&\|\(\)\[\]\{\}\^\~\?\:\"\-\\\\\/])/g, "\\$1") return '"' + input + '"'; }
0 comments :
Post a Comment