はじめに
MongoDBにおける.(ピリオド)は特殊な記号として扱われており、使用する際は注意が必要になっています。
自分も使っていく中で微妙に引っかかった点があったので、簡易的ですがまとめておきます。
オペレーションによる解釈の違い
まず、FINDやUPDATE系のクエリでは、基本的にピリオドはオブジェクト内部のフィールドを参照するものとして扱われます。
# usersコレクション内のドキュメント(_id省略)
[
{ name: 'John', favorite_colors: { red: true, blue: false, green: true } },
{ name: 'Anna', favorite_colors: { red: true, blue: true, green: true } },
{ name: 'Nancy', favorite_colors: { red: true, blue: false, green: false } },
]
# オブジェクトのフィールドをfind
> db.users.find({ 'favorite_colors.blue': true }).toArray()
< [
{ name: 'Anna', favorite_colors: { red: true, blue: true, green: true } },
]
# オブジェクトのフィールドをupdate
> db.users.updateMany({ 'favorite_colors.blue': false }, { $set: { 'favorite_colors.skyblue': false } })
# UPDATE後のusersコレクション内のドキュメント(_id省略)
[
{ name: 'John', favorite_colors: { red: true, blue: false, green: true, skyblue: false } },
{ name: 'Anna', favorite_colors: { red: true, blue: true, green: true } },
{ name: 'Nancy', favorite_colors: { red: true, blue: false, green: false, skyblue: false } },
]
上の例だと、ピリオド(.)を使用することでfavorite_colors
オブジェクトの中のフィールドがいずれも参照されています。
オブジェクト内のフィールドに簡単にアクセスすることが出来るので、便利ですね。
では、INSERT系のクエリだとどうなるでしょうか。
> db.users.insertOne({ name: 'Mark', 'favorite_colors.red': true })
# INSERTされたドキュメント(_id省略)
[
{ name: 'Mark', 'favorite_colors.red': true }
]
favorite_colors.red
という、ピリオド(.)が入ったフィールドが出来ています。
記述そのままと言えばそうなのですが、UPDATEやFIND系のクエリとは異なる挙動になっているので注意しなければいけません。
特にUPDATEとINSERTでクエリを流用するとかやっちゃうと事故の元になります。
もしオブジェクトのフィールドを作りたい場合は、ピリオドを使わずそのまま書きましょう。
例)db.users.insertOne({ name: 'Mark', favorite_colors: { red: true } })
これはVer5.0以降の挙動になっています。
それより前ではそもそもピリオド入りのフィールドが許可されておらず、エラーとなります。
UPSERTでの挙動
「えっ、じゃあupsert
を設定してたらどうなるの?」と思った方もいるかもしれません。
この場合は、結果的にinsertされる事になってもUPDATE系のクエリと同様にピリオドはオブジェクト内のフィールドを参照するようになります。
> db.users.updateOne({ name: 'Mark' }, { $set: { 'favorite_colors.red': true } }, { upsert: true })
# INSERTされたドキュメント(_id省略)
[
{ name: 'Mark', favorite_colors: { red: true } }
]
上記のような感じですね。
まあupdateとinsertでもし挙動が変わったら困るので、当たり前といえばその通りかと思います。
ピリオド(.)入りのフィールドを扱うには
...じゃあピリオド入りのフィールドってどうやってUPDATEやFINDをするんだ?オブジェクト内のフィールドを参照してしまうのでは?
と思った方もいると思います。自分もそうです。
ただ、これもちゃんと方法が用意してあります。
それが$getField
と$setField
です。
使い方はこんな感じ。
# usersコレクション内のドキュメント(_id省略)
[
{ name: 'John', favorite_colors: { red: true, blue: false, green: true }, 'favorite_colors.yellow': true },
{ name: 'Anna', favorite_colors: { red: true, blue: true, green: true }, 'favorite_colors.yellow': false },
{ name: 'Nancy', favorite_colors: { red: true, blue: false, green: false }, 'favorite_colors.yellow': true },
]
# ピリオド入りのフィールドをfind
> db.users.find({
$expr: {
$eq: [{ $getField: { field: 'favorite_colors.yellow', input: '$$CURRENT' } }, true]
}
})
.toArray()
< [
{ name: 'John', favorite_colors: { red: true, blue: false, green: true }, 'favorite_colors.yellow': true },
{ name: 'Nancy', favorite_colors: { red: true, blue: false, green: false }, 'favorite_colors.yellow': true },
]
# ピリオド入りのフィールドをupdate
> db.users.updateOne(
{ name: 'John' },
[
{
$replaceWith: {
$setField: { field: 'favorite_colors.yellow', input: '$$ROOT', value: false }
}
}
]
)
# UPDATE後のusersコレクション内のドキュメント(_id省略)
[
{ name: 'John', favorite_colors: { red: true, blue: false, green: true }, 'favorite_colors.yellow': false },
{ name: 'Anna', favorite_colors: { red: true, blue: true, green: true }, 'favorite_colors.yellow': false },
{ name: 'Nancy', favorite_colors: { red: true, blue: false, green: false }, 'favorite_colors.yellow': true },
]
めっちゃ面倒くさいけど$getField
と$setField
を通すことで、ちゃんとピリオド入りのフィールドを参照することが出来ます。
細かい使い方はあるのですが、ここで解説するよりも公式ドキュメントを参照したほうが早いので見てみてください。(参考リンクは下記)
結論
明らかに面倒なのでピリオド入りのフィールドは使わないようにしましょう。
そしてうっかりINSERTしないように注意しましょう。
ピリオド入りフィールドにしないとシステムがぶっ壊れるとか無ければね!