MongoDBのObjectId型を前方検索するのにどう書いたらいいか詰まった流れでいろいろと調べてみました。

  1. ObjectId型の_idを前方検索する
  2. $whereについて
  3. 検索条件の指定方法いろいろ
  4. Mongoidのセレクション以外での指定方法
  5. aggregateでgroup_byする


ObjectId型の_idを前方検索する

▼サンプルデータ

> db.members.find();
{ "_id" : ObjectId("54daf7307da4107a3694f5af"), "name" : "hoge", "count" : 1, "status" : 1123, "created_at" : ISODate("2015-02-11T12:20:18.766Z"), "updated_at" : ISODate("2015-02-11T12:20:18.766Z"), "deleted_at" : 0 }

MongoDBの_idはObjectId型になるので、

単純に「/^54d/」みたいな感じではヒットしませんでした。


String型に変換して「ObjectId("」から前方一致するようにすればヒットしますがううーん。

Member.where('$where' => 'function(){return this._id.match(/^ObjectId\(\"%s/)}' % Regexp.escape(@search.id))

と思ったら公式に変換するメソッドが用意されていました。

http://docs.mongodb.org/manual/reference/method/ObjectId.valueOf/


ObjectId.valueOf()を使うとカッコ内のStringを取り出せます。

ObjectId("54daf7307da4107a3694f5af").valueOf() => "54daf7307da4107a3694f5af"

これを使えば↓このように書けます。(※どちらも同じセレクタが生成されます)

Member.where('$where' => 'function(){return this._id.valueOf().match(/^%s/);}' % Regexp.escape(@search.id))
Member.where('$where' => 'this._id.valueOf().match(/^%s/)' % Regexp.escape(@search.id))

さらに、検索条件が$whereのみの場合は$whereも省略可能。

Member.where('this._id.valueOf().match(/^%s/)' % Regexp.escape(@search.id))

使いまわすようであれば、モデルの方でscopeにまとめても良いかと。

コントローラ側
Member.id(@search.id)

モデル側
scope :id, ->(id) { 'this._id.valueOf().match(/^%s/)' % Regexp.escape(id) if id.present? }

mongo Shell Methods

http://docs.mongodb.org/manual/reference/method/


IntegerもtoString()で文字列に変換すれば正規表現での検索が可能です。



$whereについて

ところで$whereですが。

http://docs.mongodb.org/manual/reference/operator/query/where/


セレクタ内でjavascriptを実行したい場合は$whereを使うようにとのこと。

公式の解説通りですが、ざっくりまとめると以下のような性質があるので

使いどころは考慮する必要があるようです。


  • インデックスが使えない
  • ネストしたセレクタ内で使えない(トップレベルで使うこと)
  • $where以外の条件と併用可能。先に$where以外のセレクタを評価する。(その結果が0の場合$where内は評価されない)
  • パフォーマンス的に微妙
  • なので$where使わずに済むならそっちを使ってね
  • どうしても使うな ら、$where以外の条件も併用して、$whereで評価される対象をある程度絞り込んでおくの推奨

  • ちなみに完全一致の場合はとてもシンプルで、

    Member.where(_id: '54daf7307da4107a3694f5af')
    

    これで↓のようなセレクタが生成されていました。

    {"_id"=>{"$all"=>[BSON::ObjectId('54daf7307da4107a3694f5af')]}}
    


検索条件の指定方法いろいろ

他にもいろいろな指定方法があります。

Mongoid Selection

http://mongoid.org/en/origin/docs/selection.html#standard

http://mongoid.org/en/origin/docs/selection.html#symbol


▼nameに「ho」を含むもの

(String型のフィールドならこの書式で正規表現が有効です)

Member.where(name: /ho/)

▼1と3のもの全部

Member.where(:status.all => [1,3])

▼1と3を含むもの

Member.where(:status.in => [1,3])

▼1と3を含まないもの

Member.where(:status.nin => [1,3])

▼statusがnil false以外

Member.where(:status.exists => true)

▼statusが1より大きい

Member.where(:status.gt => 1)

▼statusが1以上

Member.where(:status.gte => 1)

▼statusが3未満

Member.where(:status.lt => 3)

▼statusが3以下

Member.where(:category.lte => 3)

▼statusが1以外

Member.where(:category.ne => 1)

▼updated_atが2015-02-11 10:15:00.000以降

Member.where(:updated_at.gte => '2015-02-11 10:15') 

▼updated_atが2015-02-11 10:15:01.000より前

 ミリ秒があるので、「2015-02-11 10:15.725」等にもヒットさせるため1秒後のdatetimeで境界値を含まないようにする

Member.where(:updated_at.lt => Time.at(Time.parse('2015-02-11 10:15').to_i + 1))


Mongoidのセレクション以外での指定方法

Model.where()のようなMongoidのセレクション以外にも、

以下のように指定すると、MongoDBのシェルと同様の書式が使えるようです。

db = Mongoid::Sessions.default
collection = db[:models]
collection.find()

もしくは

Model.collection.find()

※「Mongoid::Sessions.default」はconfig/mongoid.ymlの設定内容?


以下はいずれも同じレコードを取得します。

mongoDB db.models.find({count: {'$gte' => 1}})
Rails Model.where(:count.gte => 1) Mongoid::Criteria
Rails Model.collection.find(count: {'$gte' => 1}) Moped::Query

ただし返ってくるオブジェクトが違っていました。

criteria = Model.where(:count.gte => 1)

pp criteria.class
 => Mongoid::Criteria

pp criteria.class.ancestors
 => [Mongoid::Criteria,
 Mongoid::Sessions::Options,
 Mongoid::Criteria::Scopable,
 Mongoid::Criteria::Modifiable,
 Mongoid::Criteria::Marshalable,
 Mongoid::Criteria::Inspectable,
 Mongoid::Criteria::Findable,
 Origin::Queryable,
 Origin::Optional,
 Origin::Selectable,
 Origin::Aggregable,
 Origin::Mergeable,
 Mongoid::Contextual,
 Enumerable,
 Object,
 Delayed::MessageSending,
 Mongoid::Extensions::Object,
 BSON::Object,
 Origin::Extensions::Object,
 ActiveSupport::Dependencies::Loadable,
 PP::ObjectMixin,
 V8::Conversion::Object,
 JSON::Ext::Generator::GeneratorMethods::Object,
 Kernel,
 BasicObject]

moped = Model.collection.find(count: {'$gte' => 1})

pp moped.class
 => Moped::Query

pp moped.class.ancestors
 => [Moped::Query,
 Mongoid::QueryCache::Query,
 Mongoid::QueryCache::Cacheable,
 Enumerable,
 Object,
 Delayed::MessageSending,
 Mongoid::Extensions::Object,
 BSON::Object,
 Origin::Extensions::Object,
 ActiveSupport::Dependencies::Loadable,
 PP::ObjectMixin,
 V8::Conversion::Object,
 JSON::Ext::Generator::GeneratorMethods::Object,
 Kernel,
 BasicObject]


aggregateでgroup_byする

MongoDBのシェルが使えるなら↓ここにあるメソッドもそのまま使えるはず…

http://docs.mongodb.org/manual/reference/method/

data = Member.collection.aggregate({'$group' => {'_id' => '$count', 'count' => {'$sum' => 1 }}})

=> [{"_id"=>3, "count"=>1}, {"_id"=>2.0, "count"=>2}, {"_id"=>1.0, "count"=>1}]

使えました!戻り値はArrayです。

pp data.class
 => Array

pp data.class.ancestors
 => [Array,
 Mongoid::Extensions::Array,
 BSON::Array,
 BSON::Encodable,
 Origin::Extensions::Array,
 Columnize,
 V8::Conversion::Array,
 JSON::Ext::Generator::GeneratorMethods::Array,
 Enumerable,
 Object,
 Delayed::MessageSending,
 Mongoid::Extensions::Object,
 BSON::Object,
 Origin::Extensions::Object,
 ActiveSupport::Dependencies::Loadable,
 PP::ObjectMixin,
 V8::Conversion::Object,
 JSON::Ext::Generator::GeneratorMethods::Object,
 Kernel,
 BasicObject]


SQL的なgroup_byとは別ですが、単純に取得結果を特定のキーでグルーピングしたい場合は

Enumerable#group_byが使えます。

(Mongoid::CriteriaもMoped::QueryもEnumerableモジュールを実装してます)

Member.where(:count.gte => 1).group_by { |member| member.status }
Member.collection.find(count: {'$gte' => 1}).group_by { |member| member[:status] }

jsファイルを渡して、コンソールからコマンド実行したりもできるらしい…便利ですね。

mongo sample_dev ~/mongo/batch.js

SQLとの比較表も公式にありました。

参考までに。

http://docs.mongodb.org/manual/reference/sql-comparison/

ブックマークに追加する

.
ウェブ開発ならAppirits
Rubyでのウェブシステム開発は
実績豊富なAppiritsにお任せ下さい
iPhone開発
iPhone開発は
Appiritsにお任せ下さい
Appirits Games Project
Appirits Games Projectはアピリッツの運営する、オンラインゲームのポータルサイトです。
オープンソースECパッケージ エレコマ
オープンソースECパッケージ
「エレコマ」
Google Analytics徹底活用スクール
レベルに合わせた
実用的な活用スキルを徹底指導。
好評定期開催中です!

プロフィール

kashuuuu!
kashuuuu!

最近チェックした記事

最新記事

アーカイブ

アクセスランキング

Ruby on railsの開発ならAppirits
株式会社アピリッツ