Edited at

こんな時どうする? MongoDBクエリ逆引きリファレンス

More than 1 year has passed since last update.


はじめに

自分はMongoDBを利用したアプリケーションの開発・運用をしばらく業務で担当していました。

業務を通じて開発・運用でよく使うであろうクエリのノウハウがある程度たまってきたので今回記事にまとめました。

「こうゆうレコード取得したいなー」「こうゆう操作ってどうやってできるんだ?」というような疑問に少しでも答えられればと思います。

特に、実際の運用でも利用する機会が多いであろう検索・更新・削除に焦点を絞って話をしたいと思います。

ですので、今回は既にMongoDBの中にレコードが存在しているという想定で話をします。

もしDockerを使える環境がある場合はMongoDBで学ぶ、Docker Machineを利用した練習用開発環境の構築手順にDockerを利用したMongoDBのインストールと今回の記事で使用しているレコード挿入の方法をまとめておきましたので併せて読んでいただければ実際にクエリを試すことができます。


使用するレコード

今回、以下のようなレコードを用意し、実際にこれらのレコードを操作することで具体例を示していきたいと思います。

なお、データベース名は test、コレクション名はstudents とします。(コレクションとはRDBでいうテーブルに該当します。)

{

"no": 1,
"firstName": "Taro",
"lastName": "Tanaka",
"age": 15,
"body": { "height": 165, "weight": 60 },
"grades": [
{ "semester": "first", "grade": 90 },
{ "semester": "second", "grade": 80 }
],
"insertDate": ISODate("2016-10-20T15:00:00Z")
}

{
"no": 2,
"firstName": "Satoshi",
"lastName": "Takeda",
"age": 16,
"body": { "height": 150, "weight": 55 },
"grades": [
{ "semester": "first", "grade": 90 },
{ "semester": "second", "grade": 70 }
],
"insertDate": ISODate("2016-10-21T15:00:00Z")
}

{
"no": 3,
"firstName": "Jiro",
"lastName": "Suzuki",
"age": 15,
"body": { "height": 170,"weight": 65 },
"grades": [
{ "semester": "first", "grade": 60 },
{ "semester": "second", "grade": 80 }
],
"insertDate": ISODate("2016-10-22T15:00:00Z")
}


基本的なクエリ


検索


基本構文

検索にはfindを利用します。条件をなにも記入しないと全件抽出されます。

db.コレクション名.find( { 条件 } )


具体例

lastNameTanakaのレコードを表示する。

> db.students.find( { "lastName": "Suzuki" } )

{ "_id" : ObjectId("580b29bc24c83b73539eab47"), "no" : 3, "firstName" : "Jiro", "lastName" : "Suzuki", "age" : 15, "body" : { "height" : 170, "weight" : 65 }, "grades" : [ { "semester" : "first", "grade" : 60 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-22T15:00:00Z") }


更新


基本構文

MongoDBの場合、デフォルトでは条件に引っかかった一番初めのレコードしか更新されません。

ですので、条件にあてはまる全てのレコードに対して更新を行いたい場合multi:trueを指定する必要があります。

db.コレクション名.update( { 条件 }, { 演算子: { 更新内容 } }, { multi: true } )

演算子はいくつかあるのですが、よく使用するものとしてはフィールドを更新する$setと、フィールドを削除する$unsetです。

補足ですが、以下のクエリも上と同意です。

第3引数のfalseupsert(レコードが存在していなければINSERT, 存在すればUPDATE)に対するオプション、第4引数のtruemultiに関するオプションです。

upsertはデフォルトでfalseなので、上記のようにmultiに関するオプションだけでよかったということです。

他のページなどでは以下のクエリを使用している場合があるので、混乱をしないように補足しておきました。

db.コレクション名.update( { 条件 }, { 演算子 : { 更新内容 } }, false, true)


具体例

lastNameTanakaのレコードのageを15から17に変更する。

> db.students.update( { "lastName": "Tanaka" }, { $set: { "age": 17 } } )

WriteResult( { "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 } )

結果の見方ですが、nMatchedは条件に該当した件数、nModifiedは更新した件数を示しています。

1件該当し、1件更新をしているので、更新は正しく完了したことを表しています。


削除


基本構文

削除にはremoveを利用します。条件をないにも記入しないと全件削除されます。

db.コレクション名.remove( { 条件 } )


具体例

lastNameTanakaのレコードを削除する。

> db.students.remove( { "lastName": "Tanaka" } )

WriteResult( { "nRemoved" : 1 } )

nRemovedは削除した件数を表します。

全件削除

> db.students.remove( { } )

WriteResult( { "nRemoved" : 3 } )


件数取得


基本構文

件数取得にはcountを利用します。条件をないにも記入しないと全件数を取得できます。

db.コレクション名.count( { 条件 } )

もしくはfindと併せることもできます。

db.コレクション名.find( { 条件 } ).count()


具体例

studentsコレクションに存在するレコード数を取得する。

> db.students.count()

3

studentsコレクションに存在するレコードのうち、ageが15の件数を取得する。

> db.students.find( { "age":15 } ).count()

2


覚えておくと役に立つクエリ


必要なところだけ表示


基本構文

MongoDBのレコードを検索するとデフォルトでは全てのフィールドを出力することになります。MongoDBに挿入される時に自動で作成される_idについては知りたいと思うことも少ないでしょうし、フィールド数が多くなると検索結果が分かりにくくなります。

表示するフィールドの変更はfindの第2引数で1と明記することで実現できます。

逆に0と明記するとそのフィールドは表示されません。

db.コレクション名.find( { 条件 },{ 表示したいフィールド: 1, ...} )


具体例

lastNameのみを出力させる。さらに_idは出力しないようにする。

> db.students.find( { }, { "_id": 0, "lastName": 1 } )

{ "lastName" : "Tanaka" }
{ "lastName" : "Takeda" }
{ "lastName" : "Suzuki" }


ネストされたオブジェクトに対する処理


基本構文

.(ドット)を区切り記号として使用することでネストされたオブジェクトに対する操作をすることができます。

db.コレクション名.find(オブジェクト.ネストされたオブジェクト)


具体例

bodyheightが150のレコードを取得する。

> db.students.find( { "body.height": 150 } )

{ "_id" : ObjectId("580b29bc24c83b73539eab46"), "no" : 2, "firstName" : "Satoshi", "lastName" : "Takeda", "age" : 16, "body" : { "height" : 150, "weight" : 55 }, "grades" : [ { "semester" : "first", "grade" : 90 }, { "semester" : "second", "grade" : 70 } ], "insertDate" : ISODate("2016-10-21T15:00:00Z") }


配列に対する更新


基本構文

オブジェクトが配列に存在する場合、値を更新するには.$.のようにドットとドル記号を利用して配列とフィールドを結合する必要があります。

先ほど説明したように、条件にあてはまる全てのレコードに対して更新を行いたい場合multi:trueを指定する必要があります。

db.コレクション名.update( { 条件 }, { 演算子: { 配列名.$.フィールド } }, { multi: true } )


具体例

lastNameTanakaのレコードに存在する、gradessemesterfirstのものに対して、grade95に変更する。

> db.students.update( { "lastName": "Tanaka", "grades.semester": "first" }, { $set: { "grades.$.grade": 95 } }, { multi: true } )

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

なお、lastNameTanakaのレコードに対して操作をする場合、直感的には以下のようなクエリを書くかもしれませんが配列の条件がない場合エラーになるので気をつけてください。

> db.students.update( { "lastName": "Tanaka" }, { $set: { "grades.$.grade": 95 } }, { multi: true } )

WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 16837,
"errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: grades.$.grade"
}
})


AND検索


基本構文

AND検索は条件を,(カンマ)でつなげることで実現できます。

db.students.find( { 条件1, 条件2, ... } )


具体例

firstNameJiro, lastNameSuzukiのレコードを取得する。

> db.students.find( { "firstName": "Jiro", "lastName": "Suzuki" } )

{ "_id" : ObjectId("580b29bc24c83b73539eab47"), "no" : 3, "firstName" : "Jiro", "lastName" : "Suzuki", "age" : 15, "body" : { "height" : 170, "weight" : 65 }, "grades" : [ { "semester" : "first", "grade" : 60 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-22T15:00:00Z") }


OR検索


基本構文

OR検索は$or演算子を利用することで実現できます。

db.コレクション名.find( { $or [ {条件1}, {条件2}, .... ] } )

もしくは$in演算子を利用することで実現できます。

db.コレクション名.find( { フィールド名: { $in: [ 値1, 値2, ...] } } )


具体例

lastNameTanakaもしくはSuzukiのレコードを取得する。

> db.students.find( { $or: [ {"lastName": "Tanaka"}, {"lastName": "Suzuki"} ] } )

{ "_id" : ObjectId("580b29bc24c83b73539eab45"), "no" : 1, "firstName" : "Taro", "lastName" : "Tanaka", "age" : 15, "body" : { "height" : 165, "weight" : 60 }, "grades" : [ { "semester" : "first", "grade" : 90 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-20T15:00:00Z") }
{ "_id" : ObjectId("580b29bc24c83b73539eab47"), "no" : 3, "firstName" : "Jiro", "lastName" : "Suzuki", "age" : 15, "body" : { "height" : 170, "weight" : 65 }, "grades" : [ { "semester" : "first", "grade" : 60 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-22T15:00:00Z") }

$in演算子を利用すると以下のようになります。

db.students.find( {  "lastName": { $in: [ "Tanaka", "Suzuki" ] } } )


部分一致検索(正規表現)


基本構文

部分一致検索は、条件を正規表現//で囲むことで実現できます。

db.students.find( { フィールド: /文字列/ } )


具体例

firstNameroを含むレコードを取得する。

> db.students.find( { "firstName": /ro/ } )

{ "_id" : ObjectId("580b29bc24c83b73539eab45"), "no" : 1, "firstName" : "Taro", "lastName" : "Tanaka", "age" : 15, "body" : { "height" : 165, "weight" : 60 }, "grades" : [ { "semester" : "first", "grade" : 90 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-20T15:00:00Z") }
{ "_id" : ObjectId("580b29bc24c83b73539eab47"), "no" : 3, "firstName" : "Jiro", "lastName" : "Suzuki", "age" : 15, "body" : { "height" : 170, "weight" : 65 }, "grades" : [ { "semester" : "first", "grade" : 60 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-22T15:00:00Z") }

補足ですが、先頭文字の一致なども正規表現を利用することで実現できます。

以下の例ではlastNameTaから始まるレコードを取得しています。

> db.students.find( { "lastName": /^Ta/ } )

{ "_id" : ObjectId("580b29bc24c83b73539eab45"), "no" : 1, "firstName" : "Taro", "lastName" : "Tanaka", "age" : 15, "body" : { "height" : 165, "weight" : 60 }, "grades" : [ { "semester" : "first", "grade" : 90 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-20T15:00:00Z") }
{ "_id" : ObjectId("580b29bc24c83b73539eab46"), "no" : 2, "firstName" : "Satoshi", "lastName" : "Takeda", "age" : 16, "body" : { "height" : 150, "weight" : 55 }, "grades" : [ { "semester" : "first", "grade" : 90 }, { "semester" : "second", "grade" : 70 } ], "insertDate" : ISODate("2016-10-21T15:00:00Z") }


「以上」「以下」検索


基本構文

「以上」「以下」といった検索条件は比較演算子を利用することで実現できます。

$gteは「以上」、$lteは「以下」の検索ができます。(eを外すと「より大きい」「未満」になります。)

db.コレクション名.find( { フィールド: { 比較演算子: 値 } } )


具体例

heightが160のレコードを取り出す。

> db.students.find( { "body.height": { $gte: 160 } } )

{ "_id" : ObjectId("580b29bc24c83b73539eab45"), "no" : 1, "firstName" : "Taro", "lastName" : "Tanaka", "age" : 15, "body" : { "height" : 165, "weight" : 60 }, "grades" : [ { "semester" : "first", "grade" : 90 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-20T15:00:00Z") }
{ "_id" : ObjectId("580b29bc24c83b73539eab47"), "no" : 3, "firstName" : "Jiro", "lastName" : "Suzuki", "age" : 15, "body" : { "height" : 170, "weight" : 65 }, "grades" : [ { "semester" : "first", "grade" : 60 }, { "semester" : "second", "grade" : 80 } ], "insertDate" : ISODate("2016-10-22T15:00:00Z") }


比較演算子一覧

比較演算子には具体例にあげたもの以外に$ne$existsなどもあり、まとめると以下のようになります。

演算子
意味
クエリ例

$lt
より小さい
{"age": {$lt: 15}}

$gt
より大きい
{"age": {$gt: 15}}

$lte
以下
{"age": {$lte: 15}}

$gte
以上
{"age": {$gte: 15}}

$ne
等しくない
{"age": {$ne: 15}}

$exists
フィールドの存在チェック
{"age": {$exists: true}}

$or
OR検索
{ $or: [ { "age": 15 }, { "age": 16} ] }


時間の検索


MongoDBでの時間の扱いについて

MongoDBにおいて、時間に関するクエリは少し考慮することがあります。

それは、MongoDBでは協定世界時(UTC)で時間が表現されており、JST(日本時間)とUTCには時差があるということです。

MongoDBにはISODate("2016-10-21T15:00:00Z")のような形で時間が格納されています。ISODateというのはMongoDBでデフォルトで利用される日時オブジェクトです。また、Zはその時間がUTCであることを示すものであり、Tは日付と時刻を分ける記号です。Tの代わりにスペースが使われることもあります。

JSTはUTCよりも9時間進んでいます。ですので、ISODate("2016-10-21T15:00:00Z")はJSTでいうところの2016年10月22日00時00分にあたります。

また、JSTの2016年10月22日00時00分はISODateを利用するとISODate('2016-10-22T00:00:00+09:00')のように表現できます。

つまり、ISODate("2016-10-21T15:00:00Z")ISODate('2016-10-22T00:00:00+09:00')は同じ時間を指していることになります。


基本構文

時間を条件とした検索は以下のように表現できます。

db.コレクション名.find( { フィールド: ISODate("時間") } )


具体例

JSTでの2016年10月22日の間(=2016年10月22日00時00分から2016年10月23日00時00分よりも前)に挿入されたレコードを取り出す。

> db.students.find( { "insertDate": { $gte: ISODate("2016-10-22T00:00:00+09:00") , $lt: ISODate("2016-10-23T00:00:00+09:00") } } )

{ "_id" : ObjectId("580b29bc24c83b73539eab46"), "no" : 2, "firstName" : "Satoshi", "lastName" : "Takeda", "age" : 16, "body" : { "height" : 150, "weight" : 55 }, "grades" : [ { "semester" : "first", "grade" : 90 }, { "semester" : "second", "grade" : 70 } ], "insertDate" : ISODate("2016-10-21T15:00:00Z") }

結果からわかるように、ISODate("2016-10-21T15:00:00Z")(=JSTでの2016年10月22日00時00分に該当)のレコードが選択されています。