0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MongoDB 配列のフィールドを更新するarrayFiltersについて

Posted at

はじめに

MongoDBのUPDATE系のオペレーション(updateOne、updateMany等)には、オプションとしてarrayFilterが存在しています。
配列のフィールドに対して更新を行う際に、その配列の中での条件を設定できる便利なオプションですね。
(元々あまりネストさせすぎると扱いにくくなるという話は置いておく)

簡単な例としては、下記のような感じです。

# test_resultコレクションのデータ(_id省略)
[{
  "no": 1,
  "name": "John",
  "tests": [
    { "subject": "math", "score": 71 },
    { "subject": "chemistry", "score": 63 },
    { "subject": "physics", "score": 55 },
    { "subject": "biology", "score": 87 }
  ]
}]

# tests内のsubject="physics"のsocreを3加算する
db.test_reslt.updateOne(
  { no: 1 },
  { $inc: { 'tests.$[test].score': 3 } },
  { arrayFilters: [{ 'test.subject': 'physics' }] }
)

# 結果
[{
  "no": 1,
  "name": "John",
  "tests": [
    { "subject": "math", "score": 71 },
    { "subject": "chemistry", "score": 63 },
    { "subject": "physics", "score": 58 },
    { "subject": "biology", "score": 87 }
  ]
}]

このようにドキュメント内のtestsフィールドが配列となっていますが、その中の特定条件の要素を指定してUPDATEを実行することができます。
UPDATEのオペレータの中(今回だと$inc)で$[{任意の識別子}]を設定し、arrayFiltersでその識別子に対する条件を設定する形で使用します。(今回だと識別子は"test"としています)
やや面倒ですが、これを使えばネストされた配列の中を操作出来るので覚えておいて損はないでしょう。

細かい制約

さて、そんなarrayFiltersですが細かい制約があります。

識別子には半角英数字しか使えず、先頭は半角英字のみ

たまに見るalphanumeric制約かつ先頭の文字の制限ですね。
つまり下記のクエリだと実行エラーになるので気をつけましょう。

# 半角英数字以外を含んでいる
db.test_reslt.updateOne(
  { no: 1 },
  { $inc: { 'tests.$[テスト].score': 3 } },
  { arrayFilters: [{ 'テスト.subject': 'physics' }] }
)

# 先頭が半角英字ではない
db.test_reslt.updateOne(
  { no: 1 },
  { $inc: { 'tests.$[Test].score': 3 } },
  { arrayFilters: [{ 'Test.subject': 'physics' }] }
)

同じ識別子は一つの要素にまとめなければならない

文章だとちょっと分かりにくいですが、要は下記のような制約です。

# test_resultコレクションのデータ(_id省略)
[{
  "no": 1,
  "name": "John",
  "tests": [
    { "subject": "math", "score": 71 },
    { "subject": "chemistry", "score": 63 },
    { "subject": "physics", "score": 55 },
    { "subject": "biology", "score": 87 }
  ]
}]

# これはエラー
db.test_result.updateOne(
  { name: 'John' },
  { $inc: { 'tests.$[test].score': 5 } },
  { arrayFilters: [{ 'test.subject': 'chemistry' }, { 'test.score': { $gte: 60 } }] }
)

# これはOK
db.test_result.updateOne(
  { name: 'John' },
  { $inc: { 'tests.$[test].score': 5 } },
  { arrayFilters: [{ 'test.subject': 'chemistry', 'test.score': { $gte: 60 } }] }
)

arrayFiltersは配列なのですが、同じ識別子なら同じ要素のオブジェクト内に記述しないとダメだよ、ということです。
まあわざわざ分けて書く意味も無いので、これは大丈夫でしょう。

さらに言うと、arrayFilters内で複数の識別子が同じ要素に入っているのもダメです。

# これはエラー
db.test_result.updateOne(
  { name: 'John' },
  { $inc: { 'tests.$[test].score': 5, 'tests.$[test2].score': 10 } },
  { arrayFilters: [{ 'test.subject': 'chemistry', 'test2.subject': 'biology' }] }
)

上記だと、識別子testtest2が同じ要素内に入っているのでダメということですね。
ちゃんと識別子ごとに要素を分ける、と考えればOKです。

しれっと書いていますが、同じフィールドに対して別々の識別子を割り当てるのは可能です。
上記だとtesttest2は同じtestsフィールドを参照しています。

多段ネストされた配列

ちなみにですが、配列の中に配列のフィールドがある場合でもこのarrayFiltersを使えば狙った要素を操作することが出来ます。

# classコレクションのデータ(_id省略)
{
  "class": "red",
  "members": [
    {
      "name": "John",
      "location": "left",
      "leader": false,
      "results": [
        { "kind": "power", "retry": 0, "score": 50 },
        { "kind": "technic", "retry": 0, "score": 70 },
        { "kind": "speed", "retry": 1, "score": 40 }
      ]
    },
    {
      "name": "Mary",
      "location": "left",
      "leader": true,
      "results": [
        { "kind": "power", "retry": 0, "score": 40 },
        { "kind": "technic", "retry": 2, "score": 60 },
        { "kind": "speed", "retry": 1, "score": 80 }
      ]
    },
    {
      "name": "Ann",
      "location": "right",
      "leader": true,
      "results": [
        { "kind": "power", "retry": 2, "score": 90 },
        { "kind": "technic", "retry": 0, "score": 50 },
        { "kind": "speed", "retry": 0, "score": 70 }
      ]
    }
  ]
}

# memberのlocation="left"かつresultのretryが1以上あるなら、そのresultのscoreを5減算する
db.class.updateOne(
  { class: 'red' },
  { $inc: { 'members.$[member].results.$[result].score': -5 } },
  { arrayFilters: [{ 'member.location': 'left' }, { 'result.retry': { $gte: 1 } }]  }
)

ここまで分かっていれば当然の書き方だと思いますが、ちゃんと対応してくれますね。
そもそもあんまりネストさせすぎると扱いにくいので、そこは考える必要がありますが…

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?