旧それなりブログの跡地、画像やスタイルやJSなどが壊れてることがあります。

[MongoDB] 値無しはnullかundefinedか

2014年7月9日

MongoDBmongoose で、「値が無い」状態の判定が null なのか undefined なのか それ以外なのかが分かり難いので、それを整理したという話です。

検証バージョンは、MongoDB 2.4.3 と mongoose 3.8.1 です。
オプションなどで動作が変わるものもあるかもしれませんが、特に何も設定していません。

undefined で更新すると null になる

> db.sandboxes.insert({ foo: undefined })
{ "_id" : ObjectId("53bd2e0d7e3d4816a9d41206"), "foo" : null }

まず、前提として undefined という値は入りません。
undefined で更新してもエラーにならず、null になっています。

null 条件で検索するとフィールド未定義の行もヒットする

> db.sandboxes.insert({})
> db.sandboxes.find({foo: null})
{ "_id" : ObjectId("53bd2f837e3d4816a9d41207") }

「foo フィールドが null の行」を検索していますが、それが未定義な行も抽出されます。

「フィールド未定義」と書きましたが、マニュアル上は「not contained」(日本語だと「値無し」?)と表現されることが多いです。

ここではSQL脳に配慮して「行」と書いていますが、普通は「ドキュメント」と呼ばれます。

Refs) How do I query for fields that have null values?

定義/未定義を条件に含めたい場合は $exists を使います。

オブジェクトを評価する際、未定義は undefined である

これが混乱する点だと思いますが、ドキュメントをオブジェクトで取得した場合、未定義のフィールドは undefined です。

> db.sandboxes.insert({foo:1, bar:2})
> doc = db.sandboxes.findOne({foo:1})
{ "_id" : ObjectId("53bd35177e3d4816a9d41209"), "foo" : 1, "bar" : 2 }
> doc.baz === null
false
> doc.baz === undefined
true

えー。

mongoose では、ドキュメントオブジェクトがラップされて返されますが、普通に評価する限りは undefined です。(null で統一してくれるアクセサがあるかもしれないが、知らない or あまり使われてない)

mongoose でフィールドに default を設定しても既存データには反映されない

いきなり mongoose の話になりますが、定義/未定義のぶれを解消しようと、フィールドへ default を設定しても:

title: {
  type: String,
  default: '--'
},

データ作成時にその値が初期値として入るだけで、既に存在する行には mongoose の抽出メソッドを使っても反映されません。

つまり、「値無し」の状態が null or undefined

例えば、「必須ではない、foo という名前の Date フィールド」を後で追加し、その「値が無い」状態を判定したい場合:

1. フィールド追加前に保存されたドキュメントでは doc.foo === undefined
2. 一度 null/undefined で更新した場合は doc.foo === null
3. それに関わらず、MongoDB で find する場合は {foo:null}

という判定を全て考慮しなければならないことになります。

最初に考えておけ

ということで、「値無し」の状態が分かり難いと思った話でした。

これを放置しておくと、テンプレのレベルで

(doc.foo === null || doc.foo === undefined) ? 'None' : doc.foo

こういう感じのコードが蔓延するようになるので、どう結論を出すにせよ、早めに整理しておいた方が良いと思います。

RDB脳なら、フィールドを増減する度に migration する、なのかなぁ。

修正: 2014-07-10

論旨がわかりにくかったので、大幅に書き換えました。