📖

MongoDBの演算子($setとか$pushとか)がわからないのでリファレンスを読んでみる

2024/08/31に公開

概要

UdemyでMEANスタックを学んでいたところ、属性の操作に唐突に$setとか$pushとかの見慣れないコードが出てきた。講師は特に触れなかったのでどうやら常識らしい。しかしわからない。 まさか他の講義購入させるためか?自分が講師の立場だったらそうするってだけだけど。 故に手を止めてリファレンスを読む。すぐ忘れるので記事にする。

mongooseの技術だと思って探したものの

ドキュメントで$pushをSearchしても見つからない [1] 。$setは見つかった[2]

Google先生に聞いてみるとMariaDBのドキュメントへのリンクを紹介された。そっちを読み進める。

$setを検索してUpdate Operators 更新演算子のページに辿り着くも

https://www.mongodb.com/docs/manual/reference/operator/update/
db.collection.updateMany() and db.collection.findAndModify().
で使えるらしい。なるほどわからん。db.collection.updateMany()のドキュメントには
mongosh Method
This page documents a mongosh method. This is not the documentation for database commands or language-specific drivers, such as Node.js.
For the database command, see the update command.
For MongoDB API drivers, refer to the language-specific MongoDB driver documentation.
と書かれていて、mongoshのメソッドだということはわかった。mongoshとは何なのか?(誤解を恐れず)纏めてしまうと MongoDBで作ったDBを操作するシェルコマンド のパッケージ?(REPL=Read Eval Print Loopって、言ってることはわかるけど‥)らしい。この中を読んでいると、 db.collection というのはMongoDBのテーブルのことのようだ。前述のUdemy講座で出てきたupdateOneメソッドがmongshのページ[3]Perform CRUD and Aggregation タブに出てきた(それ以前に前述のページでもあったけど)。どうやらこのページが$setとか$pushとかについて調べる際に参照すべきトップページと考えて良さそうだ(実際はUpdateだけ参照すれば良さそうだった)。

前提知識などの見落としもあるかもしれないため、CRUDのドキュメントに一通り目を通すことにする。

準備

下記リンクのInstall MongoDB Community with Dockrを利用して動作確認する。動作にはDockermongoshが必要。下記リンクのBefore You Beginの項でInstall手順のページへのリンクがある。
https://www.mongodb.com/docs/manual/tutorial/install-mongodb-community-with-docker/

  1. docker pull mongodb/mongodb-community-server:latest
  2. docker run --name mongodb -p 27017:27017 -d mongodb/mongodb-community-server:latest
    MongoDBサービスを動作中でポートを使っている場合は-p 27018:27017, 同じコンテナ名を使っていたりする場合は--name mongodb2にするなどで回避。
  3. mongosh --port 27017
    ホスト側ポートを変更した場合は--port 27018など起動中のコンテナに合わせる。起動に成功するとConnecting to: mongodb://127.0.0.1:27017/?...のように出力される。
  4. db.runCommand( { hello: 1 } )でコマンド発行結果を確認できれば動作準備完了。mongoshを終了するにはexitを発行。

※記事内のコマンドはすべて確認が取れたものにはなってない。一部疑問に感じたもののみ確認した。構文と結果の把握が主目的のため。というか主目的の補足学習で作成した記事 確認は前述のコンテナか、サンプルデータを利用する場合はAtlasのCommunity Editionでクラウド上で行った。

用語

use コマンド(?)

use <mydb>
db.<collection>...

というmongoshコードが頻出していてuse <mydb>するとdbが<mydb>を指すようになるらしいという解釈ができそうなものの、このふるまいの明示箇所が見つからない。どうやって探せばいいのか?検索キーワードにuse を指定しても大量にヒットする。ChatGPT先生に相談して認識が合ってるのだろうと考えたものの、裏付けができないのは不安である。
不安を無視してdb=<useで指定されているデータベース>のような認識で進めることにする。

Collection コレクション

Databases and Collections[4]において、MongoDB stores data records as documents (specifically BSON documents) which are gathered together in collections. A database stores one or more collections of documents. 意訳:MongoDBはデータレコードをドキュメントとして保存し、コレクションに纏める。データベースは1つ以上のドキュメントのコレクションが保存される。と記載されている。
コレクション=テーブルのインスタンス?と認識して進める。

Document ドキュメント、文書

MongoDBのドキュメントを眺めていて度々出てくるDocument
ドキュメントって何よ?っていう。
そもそも一般的過ぎる単語を特別な意味で使うんじゃないよと言いたい。どういう意味で使われているのか調べるのが辛いんだよ。
MongDB(やmongoose)のメソッドの説明においてはDocument Database[5]にて、
A record in MongoDB is a document, which is a data structure composed of field and value pairs. MongoDB documents are similar to JSON objects. The values of fields may include other documents, arrays, and arrays of documents.つまりはMongoDBにおけるテーブルデータのレコードの構造らしい?他のテーブルデータの構造も含むという辺りが従来のRDBMSで使われていた表現と別の名詞を割り当てた理由だろうか。※従来で意図した範囲はMySQLやPostgreSQL、Oracle辺りだが、これらにおいても自分が知らないだけでこのような表現に相当するものが存在しているのかもしれない。
ドキュメント=レコードという認識で進める。

公式ドキュメント内の説明用のサンプルDB

sample_mflix

https://www.mongodb.com/docs/atlas/sample-data/sample-mflix/#sample-mflix-dataset
The sample_mflix database contains data on movies and movie theaters. The database also contains collections for certain metadata, including users and comments on specific movies.
必要そうと判断した部分だけ抽出した意訳:
sample_mflixデータベースには映画と映画館のデータが含まれています。映画へのコメントも含みます。

sample_mflix.movies

https://www.mongodb.com/docs/atlas/sample-data/sample-mflix/#sample_mflix.movies
概要:
Contains movie information, including release year, director, and reviews.
映画の公開年、監督、レビューを含みます
コレクションの要素1つで映画1つの詳細(タイトル、公開年、レビュー)に対応
Createの操作の説明に利用されている。

Name Index Description
id { "_id": 1 } Primary key index on the _id field.
cast_text_fullplot_text_genres_text_title_text { "_fts": "text", "_ftsx": 1 } Text index on the cast, fullplot, genres, and title fields.

という情報があるものの、この部分は正直何を表現しているのかよくわからない(indexって検索速くするやつ?)。のでChatGPT先生に解説をお願いすると、
インデックス名: cast_text_fullplot_text_genres_text_title_text
インデックスの内容: { "_fts": "text", "_ftsx": 1 }
説明: これは「テキストインデックス」と呼ばれるもので、指定されたフィールドに対して全文検索を可能にするインデックスです。この場合、cast(出演者)、fullplot(あらすじ)、genres(ジャンル)、title(タイトル)フィールドに対してテキストインデックスが作成されています。

という回答が得られた。なるほど文章データの中を検索を可能に(あるいは効率よく)するために設定が必要になるというのは知らなかった。

json形式で記述されたSample Documentの情報ではmoviesコレクションの構造を説明しているようだ。

moviesコレクションのドキュメントの例:
{
  "_id": {
    "$oid": "573a1390f29313caabcd413b"
  },
  "title": "The Arrival of a Train",
  "year": {
    "$numberInt": "1896"
  },
  "runtime": {
    "$numberInt": "1"
  },
  "released": {
    "$date": {
      "$numberLong": "-2335219200000"
    }
  },
  "poster": "http://ia.media-imdb.com/images/M/MV5BMjEyNDk5MDYzOV5BMl5BanBnXkFtZTgwNjIxMTEwMzE@._V1_SX300.jpg",
  "plot": "A group of people are standing in a straight line along the
    platform of a railway station, waiting for a train, which is seen
    coming at some distance. When the train stops at the platform, ...",
  "fullplot": "A group of people are standing in a straight line along
    the platform of a railway station, waiting for a train, which is
    seen coming at some distance. When the train stops at the platform,
    the line dissolves. The doors of the railway-cars open, and people
    on the platform help passengers to get off.",
  "lastupdated": "2015-08-15 00:02:53.443000000",
  "type": "movie",
  "directors": [
    "Auguste Lumière",
    "Louis Lumière"
  ],
  "imdb": {
    "rating": {
      "$numberDouble": "7.3"
    },
    "votes": {
      "$numberInt": "5043"
    },
    "id": {
      "$numberInt": "12"
    }
  },
  "cast": [
    "Madeleine Koehler"
  ],
  "countries": [
    "France"
  ],
  "genres": [
    "Documentary",
    "Short"
  ],
  "tomatoes": {
    "viewer": {
      "rating": {
        "$numberDouble": "3.7"
      },
      "numReviews": {
        "$numberInt": "59"
      }
    },
    "lastUpdated": {
      "$date": {
        "$numberLong": "1441993589000"
      }
    }
  },
  "num_mflix_comments": {
    "$numberInt": "1"
  }
}

Create 作成 挿入

Common insert operations

  • insertOne()
    To insert a single document.
    例:

      use sample_mflix
      db.movies.insertOne(
        {
          title: "The Favourite",
          genres: [ "Drama", "History" ],
          runtime: 121,
          rated: "R",
          year: 2018,
          directors: [ "Yorgos Lanthimos" ],
          cast: [ "Olivia Colman", "Emma Stone", "Rachel Weisz" ],
          type: "movie"
        }
      )
    
  • insertMany()
    To insert multiple documents.
    例:

    use sample_mflix
    db.movies.insertMany([
     {
        title: "Jurassic World: Fallen Kingdom",
        genres: [ "Action", "Sci-Fi" ],
        runtime: 130,
        rated: "PG-13",
        year: 2018,
        directors: [ "J. A. Bayona" ],
        cast: [ "Chris Pratt", "Bryce Dallas Howard", "Rafe Spall" ],
        type: "movie"
      },
      {
        title: "Tag",
        genres: [ "Comedy", "Action" ],
        runtime: 105,
        rated: "R",
        year: 2018,
        directors: [ "Jeff Tomsic" ],
        cast: [ "Annabelle Wallis", "Jeremy Renner", "Jon Hamm" ],
        type: "movie"
      }
    ])
    

moviesコレクションにはplotやposterなどのinsertで指定していない属性もあるがこれらを指定していないことからすべての属性を指定せずにドキュメントを追加できるようである。insertで指定したフィールドにサンプルドキュメントに示されていなかったratedフィールドが含まれている。これはコレクションの中を見るとratedを持つドキュメントも混在しているようだった。MongoDBはスキーマレスなので事前に用意していない属性も追加できるらしい(とにかく裏付けのある動作であることはわかった)。
※(必要なら)コレクション=テーブル、ドキュメント=レコードのように読み替える。忘れるかもしれないので先の自分のために追記。

Read クエリ 検索

Common query operation

  • find()
    To return all documents from the sample_mflix.movies collection:
    use sample_mflix
    db.movies.find()
    
    This operation is equivalent to the following SQL statement:
    SELECT * FROM movies
    
  • find() Specify Equality Condition
    To return all movies where the title equals Titanic from the sample_mflix.movies collection:
    use sample_mflix
    db.movies.find( { "title": "Titanic" } )
    
    This operation corresponds to the following SQL statement:
    SELECT * FROM movies WHERE title = "Titanic"
    
    結果(先頭の1件のみ):
      {
          "_id": {
              "$oid": "573a139af29313caabcefb1d"
          },
          "plot": "The story of the 1912 sinking of the largest luxury liner ever built, the tragedy that befell over two thousand of the rich and famous as well as of the poor and unknown passengers aboard the doomed ship.",
          "genres": [
              "Action",
              "Drama",
              "History"
          ],
          "runtime": {
              "$numberInt": "173"
          },
          "cast": [
              "Peter Gallagher",
              "George C. Scott",
              "Catherine Zeta-Jones",
              "Eva Marie Saint"
          ],
          "poster": "https://m.media-amazon.com/images/M/MV5BYWM0MDE3OWMtMzlhZC00YzMyLThiNjItNzFhNGVhYzQ1YWM5XkEyXkFqcGdeQXVyMTczNjQwOTY@._V1_SY1000_SX677_AL_.jpg",
          "title": "Titanic",
          "fullplot": "The plot focuses on the romances of two couples upon the doomed ship's maiden voyage. Isabella Paradine (Catherine Zeta-Jones) is a wealthy woman mourning the loss of her aunt, who reignites a romance with former flame Wynn Park (Peter Gallagher). Meanwhile, a charming ne'er-do-well named Jamie Perse (Mike Doyle) steals a ticket for the ship, and falls for a sweet innocent Irish girl on board. But their romance is threatened by the villainous Simon Doonan (Tim Curry), who has discovered about the ticket and makes Jamie his unwilling accomplice, as well as having sinister plans for the girl.",
          "languages": [
              "English"
          ],
          "released": {
              "$date": {
                  "$numberLong": "848188800000"
              }
          },
          "rated": "PG-13",
          "awards": {
              "wins": {
                  "$numberInt": "0"
              },
              "nominations": {
                  "$numberInt": "9"
              },
              "text": "Won 1 Primetime Emmy. Another 8 nominations."
          },
          "lastupdated": "2015-08-30 00:47:02.163000000",
          "year": {
              "$numberInt": "1996"
          },
          "imdb": {
              "rating": {
                  "$numberDouble": "5.9"
              },
              "votes": {
                  "$numberInt": "3435"
              },
              "id": {
                  "$numberInt": "115392"
              }
          },
          "countries": [
              "Canada",
              "USA"
          ],
          "type": "series",
          "tomatoes": {
              "viewer": {
                  "rating": {
                      "$numberDouble": "3.8"
                  },
                  "numReviews": {
                      "$numberInt": "30909"
                  },
                  "meter": {
                      "$numberInt": "71"
                  }
              },
              "dvd": {
                  "$date": {
                      "$numberLong": "936662400000"
                  }
              },
              "production": "Hallmark Entertainment",
              "lastUpdated": {
                  "$date": {
                      "$numberLong": "1439662371000"
                  }
              }
          },
          "num_mflix_comments": {
              "$numberInt": "0"
          }
      }
    
  • find() Specify Conditions Using Query Operators
    To return all movies from the sample_mflix.movies collection which are either rated PG or PG-13:
    use sample_mflix
    db.movies.find( { rated: { $in: [ "PG", "PG-13" ] } } )
    
    This operation corresponds to the following SQL statement:
    SELECT * FROM movies WHERE rated in ("PG", "PG-13")
    
    結果(先頭の1件のみ):
    {
          "_id": {
              "$oid": "573a1391f29313caabcd89aa"
          },
          "plot": "Two young men, one rich, one middle class, who are in love with the same woman, become fighter pilots in World War I.",
          "genres": [
              "Drama",
              "Romance",
              "War"
          ],
          "runtime": {
              "$numberInt": "144"
          },
          "rated": "PG-13",
          "cast": [
              "Clara Bow",
              "Charles 'Buddy' Rogers",
              "Richard Arlen",
              "Jobyna Ralston"
          ],
          "num_mflix_comments": {
              "$numberInt": "1"
          },
          "poster": "https://m.media-amazon.com/images/M/MV5BYTI1YjgzZmMtZmIyYy00YTkwLTgyOWEtOTVlNTFkZmMzNTk3XkEyXkFqcGdeQXVyMDI2NDg0NQ@@._V1_SY1000_SX677_AL_.jpg",
          "title": "Wings",
          "fullplot": "Two young men fall in love with the same same girl. After the US enters WWI, both join the Air Corps and become aces. They remain friends, but the relation to the girl threatens their friendship.",
          "languages": [
              "English"
          ],
          "released": {
              "$date": {
                  "$numberLong": "-1293494400000"
              }
          },
          "directors": [
              "William A. Wellman",
              "Harry d'Abbadie d'Arrast"
          ],
          "writers": [
              "John Monk Saunders (story)",
              "Hope Loring (screenplay)",
              "Louis D. Lighton (screenplay)",
              "Julian Johnson (titles)"
          ],
          "awards": {
              "wins": {
                  "$numberInt": "5"
              },
              "nominations": {
                  "$numberInt": "1"
              },
              "text": "Won 2 Oscars. Another 3 wins & 1 nomination."
          },
          "lastupdated": "2015-09-02 00:36:34.413000000",
          "year": {
              "$numberInt": "1927"
          },
          "imdb": {
              "rating": {
                  "$numberDouble": "7.8"
              },
              "votes": {
                  "$numberInt": "7196"
              },
              "id": {
                  "$numberInt": "18578"
              }
          },
          "countries": [
              "USA"
          ],
          "type": "movie",
          "tomatoes": {
              "viewer": {
                  "rating": {
                      "$numberDouble": "3.8"
                  },
                  "numReviews": {
                      "$numberInt": "3316"
                  },
                  "meter": {
                      "$numberInt": "77"
                  }
              },
              "dvd": {
                  "$date": {
                      "$numberLong": "1327363200000"
                  }
              },
              "critic": {
                  "rating": {
                      "$numberDouble": "7.4"
                  },
                  "numReviews": {
                      "$numberInt": "37"
                  },
                  "meter": {
                      "$numberInt": "95"
                  }
              },
              "lastUpdated": {
                  "$date": {
                      "$numberLong": "1441307964000"
                  }
              },
              "consensus": "Subsequent war epics may have borrowed heavily from the original Best Picture winner, but they've all lacked Clara Bow's luminous screen presence and William Wellman's deft direction.",
              "rotten": {
                  "$numberInt": "2"
              },
              "production": "Unknown",
              "fresh": {
                  "$numberInt": "35"
              }
          }
      }
    
  • find() Specify Logical Operators (AND / OR)
    To return movies which were released in Mexico and have an IMDB rating of at least 7:
    use sample_mflix
    db.movies.find( { countries: "Mexico", "imdb.rating": { $gte: 7 } } )
    
    結果(先頭の1件のみ、メキシコのimdb.rating>=7):
    {
          "_id": {
              "$oid": "573a1393f29313caabcdd1a8"
          },
          "plot": "In Xochimilco 1909, Marèa Candelaria and Lorenzo Rafael long for getting married but the odds are against them. Maria Candelaria is segregated for being a prostitute's child and the couple ...",
          "genres": [
              "Romance",
              "Drama"
          ],
          "runtime": {
              "$numberInt": "76"
          },
          "rated": "APPROVED",
          "cast": [
              "Dolores del Rio",
              "Pedro Armendèriz",
              "Alberto Galèn",
              "Margarita Cortès"
          ],
          "poster": "https://m.media-amazon.com/images/M/MV5BOTFlYTk2MGYtOGI0Yy00YjJhLWE1NjEtMTMwYjIxYWRmNzcxXkEyXkFqcGdeQXVyNjc0MzMzNjA@._V1_SY1000_SX677_AL_.jpg",
          "title": "Marèa Candelaria (Xochimilco)",
          "fullplot": "In Xochimilco 1909, Marèa Candelaria and Lorenzo Rafael long for getting married but the odds are against them. Maria Candelaria is segregated for being a prostitute's child and the couple faces the greed of Mr. Damian, the town's shop owner who secretly desires Maria. When Maria falls sick with Malaria, Lorenzo Rafael steals a quinine bottle from Damian's shop, unleashing tragedy for the lovers.",
          "languages": [
              "Spanish"
          ],
          "released": {
              "$date": {
                  "$numberLong": "-798595200000"
              }
          },
          "directors": [
              "Emilio Fernèndez"
          ],
          "writers": [
              "Emilio Fernèndez",
              "Mauricio Magdaleno"
          ],
          "awards": {
              "wins": {
                  "$numberInt": "3"
              },
              "nominations": {
                  "$numberInt": "0"
              },
              "text": "3 wins."
          },
          "lastupdated": "2015-06-11 18:19:37.473000000",
          "year": {
              "$numberInt": "1944"
          },
          "imdb": {
              "rating": {
                  "$numberDouble": "7.7"
              },
              "votes": {
                  "$numberInt": "594"
              },
              "id": {
                  "$numberInt": "37054"
              }
          },
          "countries": [
              "Mexico"
          ],
          "type": "movie",
          "tomatoes": {
              "viewer": {
                  "rating": {
                      "$numberDouble": "3.9"
                  },
                  "numReviews": {
                      "$numberInt": "99"
                  },
                  "meter": {
                      "$numberInt": "87"
                  }
              },
              "lastUpdated": {
                  "$date": {
                      "$numberLong": "1442078066000"
                  }
              }
          },
          "num_mflix_comments": {
              "$numberInt": "0"
          }
      }
    
    To return movies from the sample_mflix.movies collection which were released in 2010 and either won at least 5 awards or have a genre of Drama:
    use sample_mflix
    db.movies.find( {
       year: 2010,
       $or: [ { "awards.wins": { $gte: 5 } }, { genres: "Drama" } ]
    } )
    
    結果(先頭の1件のみ、2010年のドラマ):
    {
          "_id": {
              "$oid": "573a139cf29313caabcf6fb1"
          },
          "plot": "A lonely obese nurse, working at a hospital terminal ward, is reminded of her childhood friend Adrienn Pal and wants to track her down.",
          "genres": [
              "Drama"
          ],
          "runtime": {
              "$numberInt": "136"
          },
          "cast": [
              "èva Gèbor",
              "Istvèn Znamenèk",
              "èkos Horvèth",
              "Lia Pokorny"
          ],
          "num_mflix_comments": {
              "$numberInt": "1"
          },
          "poster": "https://m.media-amazon.com/images/M/MV5BODNjZmM3MWYtYmUwZC00M2RjLTkyZmEtYzRhMDU1NmM3ZmIzXkEyXkFqcGdeQXVyOTAzODAwOQ@@._V1_SY1000_SX677_AL_.jpg",
          "title": "Pèl Adrienn",
          "fullplot": "A lonely obese nurse, working at a hospital terminal ward, is reminded of her childhood friend Adrienn Pal and wants to track her down.",
          "languages": [
              "Hungarian"
          ],
          "released": {
              "$date": {
                  "$numberLong": "1300320000000"
              }
          },
          "directors": [
              "ègnes Kocsis"
          ],
          "writers": [
              "ègnes Kocsis (screenplay)",
              "Andrea Roberti (screenplay)"
          ],
          "awards": {
              "wins": {
                  "$numberInt": "4"
              },
              "nominations": {
                  "$numberInt": "5"
              },
              "text": "4 wins & 5 nominations."
          },
          "lastupdated": "2015-09-01 01:00:29.427000000",
          "year": {
              "$numberInt": "2010"
          },
          "imdb": {
              "rating": {
                  "$numberDouble": "6.8"
              },
              "votes": {
                  "$numberInt": "348"
              },
              "id": {
                  "$numberInt": "146592"
              }
          },
          "countries": [
              "Hungary",
              "France",
              "Austria",
              "Netherlands"
          ],
          "type": "movie",
          "tomatoes": {
              "viewer": {
                  "rating": {
                      "$numberDouble": "3.5"
                  },
                  "numReviews": {
                      "$numberInt": "40"
                  },
                  "meter": {
                      "$numberInt": "67"
                  }
              },
              "lastUpdated": {
                  "$date": {
                      "$numberLong": "1437589092000"
                  }
              }
          }
      }
    

"imdb.rating": { $gte: 7 } = "imdb": { "rating": { "$numberDouble": "7.3" } } >= 7
$gte=greater than equal
$gte: 5 = value >= 5

Update 更新 変更

https://www.mongodb.com/docs/mongodb-shell/crud/update/#update-documents

Common update operations

prepare "Twilight"
db.movies.insertOne({
    "plot": "A teenage girl risks everything when she falls in love with a vampire.",
    "genres": [
        "Drama",
        "Fantasy",
        "Romance"
    ],
    "rated": "PG-13",
    "cast": [
        "Kristen Stewart",
        "Sarah Clarke",
        "Matt Bushell",
        "Billy Burke"
    ],
    "title": "Twilight"
})
  • db.collection.updateOne()
    To update the first document in the sample_mflix.movies collection where title equals "Twilight":
    use sample_mflix
    db.movies.updateOne( { title: "Twilight" },
    {
        $set: {
            plot: "A teenage girl risks everything–including her life–when she falls in love with a vampire."
        },
        $currentDate: { lastUpdated: true }
    })
    
  • db.collection.updateMany()
    ここで出てくるデータベースsample_airbnbAtlas上でサンプルデータのロードを行うと使えるようになるとここに記載されていたが、やってみると使えるようになるデータベースはsample_mflixのみだった。Ask MongoDB AIで問い合わせてみると画面全体が白くなるだけだったため深追いしない。それは聞かないで的な?
    To update all documents in the sample_airbnb.listingsAndReviews collection to update where security_deposit is less than 100:
    use sample_airbnb
    db.listingsAndReviews.updateMany(
        { security_deposit: { $lt: 100 } },
        {
            $set: { security_deposit: 100, minimum_nights: 1 }
        }
    )
    
    sample_mflixを使った代替:
    use sample_mflix
    // check the documents to change command
    db.movies.find( { year: { $eq: 2018 }, rated: { $eq: 'R' } } )
    // Update type and mytag for 2018 rated R movies
    db.movies.updateMany(
        { year: { $eq: 2018 }, rated: { $eq: 'R' } },
        {
            $set: { type: 'specially_restricted_movie', mytag: 'modified' }
        }
    )
    // check result command
    db.movies.find( { year: { $eq: 2018 }, rated: { $eq: 'R' } } )
    // Undo
    db.movies.updateMany(
        { year: { $eq: 2018 }, rated: { $eq: 'R' } },
        {
            $set: { type: 'movie' },
            $unset: { mytag: "" }
        }
    )
    
  • db.collection.replaceOne()
    To replace the first document from the sample_analytics.accounts collection where account_id: 371138:
    db.accounts.replaceOne(
        { account_id: 371138 },
        { account_id: 893421, limit: 5000, products: [ "Investment", "Brokerage" ] }
    )
    
    sample_mflixを使った代替:
    use sample_mflix
    // prepare
    db.movies.insertOne( { title: 'Tonikaku akarui', genres: [ 'Comedy' ] } )
    db.movies.replaceOne(
        { title: 'Tonikaku akarui' },
        { title: 'Tony', genres: [ 'Comedy' ], year: 2023, rated: 'R18' }
    )
    // check result command
    db.movies.find( { title: 'Tony' } )
    

updateOneメソッドで元々の調査対象である$setが登場。plotを指定文字列に更新して、$currentDateで更新日を更新?これらはupdate operatorという名前で呼ばれているようだ。

Update Operator Syntax

{
  <update operator>: { <field1>: <value1>, ... },
  <update operator>: { <field2>: <value2>, ... },
  ...
}

$setや$currentDateがupdate operatorらしい。

update operators

https://www.mongodb.com/docs/manual/reference/operator/update/#update-operators
機能が使える環境は

  • MongoDB Atlas
  • MongoDB Enterprise
  • MongoDB Community
    The source-available, free-to-use, and self-managed version of MongoDB

現状ではCommunity版しか利用予定がなさそうなのでここだけ説明を記載。
ローカル構築(from dockerhub)なのでCommunity版を使っている状態のようだ。

Fields:

Name 説明 原文
$currentDate フィールドの値を現在の日付か時刻に設定する Sets the value of a field to current date, either as a Date or a Timestamp.
$inc フィールドの値を指定量インクリメントする Increments the value of the field by the specified amount.
$min 指定値とフィールドの値で小さい方をフィールドの値に設定する Only updates the field if the specified value is less than the existing field value.
$max 指定値とフィールドの値で大きい方をフィールドの値に設定する Only updates the field if the specified value is greater than the existing field value.
$mul フィールドの値と指定値の乗算を行う Multiplies the value of the field by the specified amount.
$rename フィールドをリネームする Renames a field.
$set フィールドに値を設定する Sets the value of a field in a document.
$setOnInsert ドキュメントを新規追加する場合はフィールドに値を設定する Sets the value of a field if an update results in an insert of a document. Has no effect on update operations that modify existing documents.
$unset ドキュメントからフィールドを削除する Removes the specified field from a document.
$inc

syntax:

{ $inc: { <field1>: <amount1>, <field2>: <amount2>, ... } }

example:

db.products.insertOne(
   {
     _id: 1,
     sku: "abc123",
     quantity: 10,
     metrics: { orders: 2, ratings: 3.5 }
   }
)
// set quantity += -2, metrics.orders += 1 if document.sku == "abc123"
db.products.updateOne(
   { sku: "abc123" },
   { $inc: { quantity: -2, "metrics.orders": 1 } }
)
// result
{
  _id: 1,
  sku: 'abc123',
  quantity: 8,
  metrics: { orders: 3, ratings: 3.5 }
}
$min

syntax:

{ $min: { <field1>: <value1>, ... } }

example:

db.scores.insertOne( { _id: 1, highScore: 800, lowScore: 200 } )
// lowScore = 150 if 150 < document.lowScore else document.lowScore
db.scores.updateOne( { _id: 1 }, { $min: { lowScore: 150 } } )
// result
{ _id: 1, highScore: 800, lowScore: 150 }
// no effect operation
db.scores.updateOne( { _id: 1 }, { $min: { lowScore: 250 } } )
{ _id: 1, highScore: 800, lowScore: 150 }
$mul

https://www.mongodb.com/docs/manual/reference/operator/update/mul/#mixed-type
syntax:

{ $mul: { <field1>: <number1>, ... } }

example:

db.products.insertOne(
   { "_id" : 1, "item" : "Hats", "price" : Decimal128("10.99"), "quantity" : 25 }
)
db.products.updateOne(
   { _id: 1 },
   { $mul:
      {
         price: Decimal128( "1.25" ),
         quantity: 2
       }
   }
)
// result
{ _id: 1, item: 'Hats', price: Decimal128("13.7375"), quantity: 50 }
$rename

syntax:

{ $rename: { <field1>: <newName1>, <field2>: <newName2>, ... } }

example:

// The preceding operation renames the nickname field to alias and the cell field to mobile in a document where _id is 1.
db.students.updateOne(
   { _id: 1 }, { $rename: { 'nickname': 'alias', 'cell': 'mobile' } }
)

example2 rename field name nmae -> name:

db.students.insertMany( [
   {
     "_id": 1,
     "alias": [ "The American Cincinnatus", "The American Fabius" ],
     "mobile": "555-555-5555",
     "nmae": { "first" : "george", "last" : "washington" }
   },
   {
     "_id": 2,
     "alias": [ "My dearest friend" ],
     "mobile": "222-222-2222",
     "nmae": { "first" : "abigail", "last" : "adams" }
   },
   {
     "_id": 3,
     "alias": [ "Amazing grace" ],
     "mobile": "111-111-1111",
     "nmae": { "first" : "grace", "last" : "hopper" }
   }
] )
// rename
db.students.updateMany(
   { "nmae": { $ne: null } },
   { $rename: { "nmae": "name" } }
)
// result
{
  "_id": 1,
  "alias": [ "The American Cincinnatus", "The American Fabius" ],
  "mobile": "555-555-5555",
  "name": { "first" : "george", "last" : "washington" }
}

{
   "_id" : 2,
   "alias" : [ "My dearest friend" ],
   "mobile" : "222-222-2222",
   "name" : { "first" : "abigail", "last" : "adams" }
}

{
   "_id" : 3,
  "alias" : [ "Amazing grace" ],
  "mobile" : "111-111-1111",
  "name" : { "first" : "grace", "last" : "hopper" }
}
$setOnInsert

syntax:

db.collection.updateOne(
   <query>,
   { $setOnInsert: { <field1>: <value1>, ... } },
   { upsert: true }
)

example:

// Insert a new document using db.collection.updateOne() the upsert: true parameter.
db.products.updateOne(
  { _id: 1 },
  {
     $set: { item: "apple" },
     $setOnInsert: { defaultQty: 100 }
  },
  { upsert: true }
)
// result
{ "_id" : 1, "item" : "apple", "defaultQty" : 100 }

$unset

syntax:

{ $unset: { <field1>: "", ... } }

example:

db.products.insertMany( [
   { "item": "chisel", "sku": "C001", "quantity": 4, "instock": true },
   { "item": "hammer", "sku": "unknown", "quantity": 3, "instock": true },
   { "item": "nails", "sku": "unknown", "quantity": 100, "instock": true }
] )

// Update the first document in the products collection where the value of sku is unknown:
db.products.updateOne(
   { sku: "unknown" },
   { $unset: { quantity: "", instock: "" } }
)

// result
{
  item: 'chisel',
  sku: 'C001',
  quantity: 4,
  instock: true
},
{
  item: 'hammer',
  sku: 'unknown'
},
{
  item: 'nails',
  sku: 'unknown',
  quantity: 100,
  instock: true
}

Array operators:

Name 説明 原文
$ クエリ条件に一致する最初の要素を更新するためのプレースホルダー Acts as a placeholder to update the first element that matches the query condition.
$[] クエリ条件に一致する配列のすべての要素を更新するためのプレースホルダー Acts as a placeholder to update all elements in an array for the documents that match the query condition.
$[<identifier>] クエリ条件に一致するドキュメントの配列フィルタ条件に一致するすべての要素を更新するためのプレースホルダー Acts as a placeholder to update all elements that match the arrayFilters condition for the documents that match the query condition.
$addToSet 要素が存在しない場合に配列に要素を追加 Adds elements to an array only if they do not already exist in the set.
$pop 配列の最初か最後の要素を削除 Removes the first or last item of an array.
$pull クエリに一致するすべての配列要素を削除 Removes all array elements that match a specified query.
$push 配列に要素を追加 Adds an item to an array.
$pullAll 配列から一致する値をすべて削除 Removes all matching values from an array.
$

syntax:

{ "<array>.$" : value }
db.collection.updateOne(
   { <array>: value ... },
   { <update operator>: { "<array>.$" : value } }
)

example Update Values in an Array:

db.students.insertMany( [
   { "_id" : 1, "grades" : [ 85, 80, 80 ] },
   { "_id" : 2, "grades" : [ 88, 90, 92 ] },
   { "_id" : 3, "grades" : [ 85, 100, 90 ] }
] )

// grades[grades==80] = 82 ※first match
db.students.updateOne(
   { _id: 1, grades: 80 },
   { $set: { "grades.$" : 82 } }
)

// result
{ "_id" : 1, "grades" : [ 85, 82, 80 ] }
{ "_id" : 2, "grades" : [ 88, 90, 92 ] }
{ "_id" : 3, "grades" : [ 85, 100, 90 ] }

example Update Documents in an Array:

{
  _id: 4,
  grades: [
     { grade: 80, mean: 75, std: 8 },
     { grade: 85, mean: 90, std: 5 },
     { grade: 85, mean: 85, std: 8 }
  ]
}

db.students.updateOne(
   { _id: 4, "grades.grade": 85 },
   { $set: { "grades.$.std" : 6 } }
)

// result
{
   "_id" : 4,
   "grades" : [
      { "grade" : 80, "mean" : 75, "std" : 8 },
      { "grade" : 85, "mean" : 90, "std" : 6 },
      { "grade" : 85, "mean" : 85, "std" : 8 }
   ]
}

example Update Embedded Documents Using Multiple Field Matches:

{
  _id: 5,
  grades: [
     { grade: 80, mean: 75, std: 8 },
     { grade: 85, mean: 90, std: 5 },
     { grade: 90, mean: 85, std: 3 }
  ]
}

// set grades[ grades.grade <= 90 and grades.mean > 80 ].std = 6 ※
db.students.updateOne(
   {
     _id: 5,
     grades: { $elemMatch: { grade: { $lte: 90 }, mean: { $gt: 80 } } }
   },
   { $set: { "grades.$.std" : 6 } }
)

// result only first match
{
  _id: 5,
  grades: [
    { grade: 80, mean: 75, std: 8 },
    { grade: 85, mean: 90, std: 6 },
    { grade: 90, mean: 85, std: 3 }
  ]
}
$[]

syntax:

{ <update operator>: { "<array>.$[]" : value } }
// use in updateOne()
db.collection.updateOne(
   { <query conditions> },
   { <update operator>: { "<array>.$[]" : value } }
)

example upsert:

db.collection.updateOne(
   { myArray: [ 5, 8 ] },
   { $set: { "myArray.$[]": 10 } },
   { upsert: true }
)

// result
{ "_id" : ObjectId(...), "myArray" : [ 10, 10 ] }

example operation would error:

db.emptyCollection.updateOne(
   { },
   { $set: { "myArray.$[]": 10 } },
   { upsert: true }
)

db.emptyCollection.updateOne(
   { myArray: 5 },
   { $set: { "myArray.$[]": 10 } },
   { upsert: true }
)

example Update All Elements in an Array:

db.students.insertMany( [
   { "_id" : 1, "grades" : [ 85, 82, 80 ] },
   { "_id" : 2, "grades" : [ 88, 90, 92 ] },
   { "_id" : 3, "grades" : [ 85, 100, 90 ] }
] )
// set grades[all] += 10
db.students.updateMany(
   { },
   { $inc: { "grades.$[]": 10 } },
)
// result
{ "_id" : 1, "grades" : [ 95, 92, 90 ] }
{ "_id" : 2, "grades" : [ 98, 100, 102 ] }
{ "_id" : 3, "grades" : [ 95, 110, 100 ] }

example Update All Documents in an Array:

db.students2.insertMany( [
   {
      "_id" : 1,
      "grades" : [
         { "grade" : 80, "mean" : 75, "std" : 8 },
         { "grade" : 85, "mean" : 90, "std" : 6 },
         { "grade" : 85, "mean" : 85, "std" : 8 }
      ]
   },
   {
      "_id" : 2,
      "grades" : [
         { "grade" : 90, "mean" : 75, "std" : 8 },
         { "grade" : 87, "mean" : 90, "std" : 5 },
         { "grade" : 85, "mean" : 85, "std" : 6 }
      ]
   }
] )

// set grades[all].std += -2
db.students2.updateMany(
   { },
   { $inc: { "grades.$[].std" : -2 } },
)

// result
{
   "_id" : 1,
   "grades" : [
      { "grade" : 80, "mean" : 75, "std" : 6 },
      { "grade" : 85, "mean" : 90, "std" : 4 },
      { "grade" : 85, "mean" : 85, "std" : 6 }
   ]
}
{
   "_id" : 2,
   "grades" : [
      { "grade" : 90, "mean" : 75, "std" : 6 },
      { "grade" : 87, "mean" : 90, "std" : 3 },
      { "grade" : 85, "mean" : 85, "std" : 4 }
   ]
}

example Update Arrays Specified Using a Negation Query Operator:

db.results.insertMany( [
   { "_id" : 1, "grades" : [ 85, 82, 80 ] },
   { "_id" : 2, "grades" : [ 88, 90, 92 ] },
   { "_id" : 3, "grades" : [ 85, 100, 90 ] }
] )

// set grades[grades != 100] += 10
db.results.updateMany(
   { "grades" : { $ne: 100 } },
   { $inc: { "grades.$[]": 10 } },
)

// result
{ "_id" : 1, "grades" : [ 95, 92, 90 ] }
{ "_id" : 2, "grades" : [ 98, 100, 102 ] }
{ "_id" : 3, "grades" : [ 85, 100, 90 ] }
$neの動きに疑問。これはnot inでは?に対する調査:

ne=not equal解釈はMongoDB Docsで合っていることが確認できた。配列フィールドに対する`neだとnot ingrades != 100だとすべて更新対象になりそうだし、gradesの要素 != 100だと_id:3のgradesは[95, 100, 100]になると考えていたので違和感がある。ne`を配列フィールドに対してフィルタリングする場合どうなるかをMongoDB Manual内で探した結果そのような例を見つけることができなかった。ので、手元の環境にインストールして`db.results.find({"grades": {ne: 100}})`の動作を確認したところ、実際に

[
  { _id: 1, grades: [ 85, 82, 80 ] },
  { _id: 2, grades: [ 88, 90, 92 ] }
]

のような結果になることを確認できた。そして、db.results.find({"grades": {$nin: [100]}})でも同様の結果を返した。

$[<identifier>]

syntax:

db.collection.updateMany(
   { <query conditions> },
   { <update operator>: { "<array>.$[<identifier>]" : value } },
   { arrayFilters: [ { <identifier>: <condition> } ] }
)

The arrayFilters option cannot include the following query operators:

  • $expr
  • $text
  • $where

example:

db.collection.updateOne(
   { myArray: [ 0, 1 ] },
   { $set: { "myArray.$[element]": 2 } },
   { arrayFilters: [ { element: 0 } ], upsert: true }
)

// check result command
db.collection.find()

// result
{ "_id" : ObjectId(...), "myArray" : [ 2, 1 ] }

example did not include on exact equality match and not matching documents were found to update, this operation would error:

// error
db.array.updateOne(
   { },
   { $set: { "myArray.$[element]": 10 } },
   { arrayFilters: [ { element: 9 } ], upsert: true }
)

// MongoServerError: The path 'myArray' must exist in the document in order to apply array updates.

example:

db.students.insertMany( [
   { "_id" : 1, "grades" : [ 95, 92, 90 ] },
   { "_id" : 2, "grades" : [ 98, 100, 102 ] },
   { "_id" : 3, "grades" : [ 95, 110, 100 ] }
] )
db.students.find()

// set grades[grades >= 100] = 100.
db.students.updateMany(
   { },
   { $set: { "grades.$[element]" : 100 } },
   { arrayFilters: [ { "element": { $gte: 100 } } ] }
)

// check result command
db.students.find()

// result
{ "_id" : 1, "grades" : [ 95, 92, 90 ] }
{ "_id" : 2, "grades" : [ 98, 100, 100 ] }
{ "_id" : 3, "grades" : [ 95, 100, 100 ] }

syntax Update All Documents That Match arrayFilters in an Array:

db.collection.updateMany(
   { <query selector> },
   { <update operator>: { "array.$[<identifier>].field" : value } },
   { arrayFilters: [ { <identifier>: <condition> } } ] }
)

example:

db.students2.insertMany( [
   {
      "_id" : 1,
      "grades" : [
         { "grade" : 80, "mean" : 75, "std" : 6 },
         { "grade" : 85, "mean" : 90, "std" : 4 },
         { "grade" : 85, "mean" : 85, "std" : 6 }
      ]
   },
   {
      "_id" : 2,
      "grades" : [
         { "grade" : 90, "mean" : 75, "std" : 6 },
         { "grade" : 87, "mean" : 90, "std" : 3 },
         { "grade" : 85, "mean" : 85, "std" : 4 }
      ]
   }
] )
db.students2.find()

// set grades[grades.mean>=85].mean = 100
db.students2.updateMany(
   { },
   { $set: { "grades.$[elem].mean" : 100 } },
   { arrayFilters: [ { "elem.grade": { $gte: 85 } } ] }
)

// check result command
db.students2.find()

// result
{
   "_id" : 1,
   "grades" : [
      { "grade" : 80, "mean" : 75, "std" : 6 },
      { "grade" : 85, "mean" : 100, "std" : 4 },
      { "grade" : 85, "mean" : 100, "std" : 6 }
   ]
}
{
   "_id" : 2,
   "grades" : [
      { "grade" : 90, "mean" : 100, "std" : 6 },
      { "grade" : 87, "mean" : 100, "std" : 3 },
      { "grade" : 85, "mean" : 100, "std" : 4 }
   ]
}

example Update All Array Elements that Match Multiple Conditions:

db.students3.insertMany( [
   {
      "_id" : 1,
      "grades" : [
         { "grade" : 80, "mean" : 75, "std" : 6 },
         { "grade" : 85, "mean" : 100, "std" : 4 },
         { "grade" : 85, "mean" : 100, "std" : 6 }
      ]
   },
   {
      "_id" : 2,
      "grades" : [
         { "grade" : 90, "mean" : 100, "std" : 6 },
         { "grade" : 87, "mean" : 100, "std" : 3 },
         { "grade" : 85, "mean" : 100, "std" : 4 }
      ]
   }
] )
db.students3.find()

// set grades[grades.grade >= 80 and grades.std >= 5].std += -1
db.students3.updateMany(
   { },
   { $inc: { "grades.$[elem].std" : -1 } },
   { arrayFilters: [ { "elem.grade": { $gte: 80 }, "elem.std": { $gte: 5 } } ] })
db.students3.find()

// result
{  "_id" : 1,
   "grades" : [
      { "grade" : 80, "mean" : 75, "std" : 5 },
      { "grade" : 85, "mean" : 100, "std" : 4 },
      { "grade" : 85, "mean" : 100, "std" : 5 }
   ]
}
{
   "_id" : 2,
   "grades" : [
      { "grade" : 90, "mean" : 100, "std" : 5 },
      { "grade" : 87, "mean" : 100, "std" : 3 },
      { "grade" : 85, "mean" : 100, "std" : 4 }
   ]
}
$addToSet

syntax:

{ $addToSet: { <field1>: <value1>, ... } }

example(Fail) Field Not an Array:

db.pigments.insertOne( { _id: 1, colors: "blue, green, red" } )
// will fail
db.pigments.updateOne(
   { _id: 1 },
   { $addToSet: { colors: "mauve" } }
)

example Value to Add is An Array:

db.alphabet.insertOne( { _id: 1, letters: ["a", "b"] } )
db.alphabet.updateOne(
   { _id: 1 },
   { $addToSet: { letters: [ "c", "d" ] } }
)
db.alphabet.find()

// result
{ _id: 1, letters: [ 'a', 'b', [ 'c', 'd' ] ] }

letters: [ 'a', 'b', 'c', 'd' ] のようにしたい場合は$addToSet$each Modifierと共に利用する。この記事のArray modifiers:$addToSet.$eachを参照。

$pop

syntax:

{ $pop: { <field>: <-1 | 1>, ... } }

example Remove the First Item of an Array:

db.students.insertOne( { _id: 1, scores: [ 8, 9, 10 ] } )
db.students.updateOne( { _id: 1 }, { $pop: { scores: -1 } } )

// check result command
db.students.find( { _id: 1 } )

// result
{ _id: 1, scores: [ 9, 10 ] }

example Remove the Last Item of an Array:

db.students.insertOne( { _id: 10, scores: [ 9, 10 ] } )
db.students.updateOne( { _id: 10 }, { $pop: { scores: 1 } } )

// check result command
db.students.find({_id:10})

// result
{ _id: 10, scores: [ 9 ] }
$pull

syntax:

{ $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }

example remove all items that equal a specified value:

db.stores.insertMany( [
   {
      _id: 1,
      fruits: [ "apples", "pears", "oranges", "grapes", "bananas" ],
      vegetables: [ "carrots", "celery", "squash", "carrots" ]
   },
   {
      _id: 2,
      fruits: [ "plums", "kiwis", "oranges", "bananas", "apples" ],
      vegetables: [ "broccoli", "zucchini", "carrots", "onions" ]
   }
] )
db.stores.find()

// remove apple and orages from fruits, and remove carrots from vegetables
db.stores.updateMany(
    { },
    { $pull: { fruits: { $in: [ "apples", "oranges" ] }, vegetables: "carrots" } }
)
db.stores.find()

// result
{
  _id: 1,
  fruits: [ 'pears', 'grapes', 'bananas' ],
  vegetables: [ 'celery', 'squash' ]
},
{
  _id: 2,
  fruits: [ 'plums', 'kiwis', 'bananas' ],
  vegetables: [ 'broccoli', 'zucchini', 'onions' ]
}

example remove all items that match a specified $pull condition(>=6):

db.profiles.insertOne( { _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] } )
db.profiles.find()

// remove votes[votes >= 6]
db.profiles.updateOne( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
db.profiles.find()

// result
{ _id: 1, votes: [  3,  5 ] }

example remove all items that match a specified $pull condition with bulkWrite():
https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-all-items-that-match-a-specified--pull-condition-with

try {
   db.profilesBulkWrite.bulkWrite( [
      {
         insertOne: {
            "document": { _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
         }
      },
      {
         updateOne: {
            "filter": { _id: 1 },
            "update": { $pull: { votes: { $gte: 6 } } }
         }
      },
      {
         updateOne: {
            "filter": {_id: 1},
            "update": { $pull: { votes: { $lte: 3 } } }
         }
      }
   ] );
} catch (e) {
   print(e);
}
db.profilesBulkWrite.find()

// result
[ { _id: 1, votes: [ 5 ] } ]

example remove from an array of documents:

db.survey.insertMany([
   {
      _id: 1,
      results: [
         { item: "A", score: 5 },
         { item: "B", score: 8 }
      ]
   },
   {
      _id: 2,
      results: [
         { item: "C", score: 8 },
         { item: "B", score: 4 }
      ]
   }
] )
db.survey.find()

// pull score = 8 and item = "B" 
db.survey.updateMany(
  { },
  { $pull: { results: { score: 8 , item: "B" } } }
)
db.survey.find()

// result after pulled
{ _id: 1, results: [ { item: 'A', score: 5 } ] },
{
  _id: 2,
  results: [ { item: 'C', score: 8 }, { item: 'B', score: 4 } ]
}

example remove documents from nested arrays:

db.survey.drop()
db.survey.insertMany( [
   {
      _id: 1,
      results: [
         {
            item: "A",
            score: 5,
            answers: [ { q: 1, a: 4 }, { q: 2, a: 6 } ]
         },
         {
            item: "B",
            score: 8,
            answers: [ { q: 1, a: 8 }, { q: 2, a: 9 } ]
         }
      ]
   },
   {
      _id: 2,
      results: [
         {
            item: "C",
            score: 8,
            answers: [ { q: 1, a: 8 }, { q: 2, a: 7 } ]
         },
         {
            item: "B",
            score: 4,
            answers: [ { q: 1, a: 0 }, { q: 2, a: 8 } ]
         }
      ]
   }
] )
db.survey.find()

// pull condition: q=2, a>=8
db.survey.updateMany(
  { },
  {
     $pull:
        {
           results:
              {
                 answers: { $elemMatch: { q: 2, a: { $gte: 8 } } }
              }
        }
  }
)
db.survey.find()

// result after pulled
{
  _id: 1,
  results: [
    {
      item: 'A',
      score: 5,
      answers: [ { q: 1, a: 4 }, { q: 2, a: 6 } ]
    }
  ]
},
{
  _id: 2,
  results: [
    {
      item: 'C',
      score: 8,
      answers: [ { q: 1, a: 8 }, { q: 2, a: 7 } ]
    }
  ]
}
$push

syntax:

{ $push: { <field1>: <value1>, ... } }

example:

db.students.insertOne( { _id: 1, scores: [ 44, 78, 38, 80 ] } )
db.students.find()
db.students.updateOne(
   { _id: 1 },
   { $push: { scores: 89 } }
)
db.students.find()

// result
{ _id: 1, scores: [ 44, 78, 38, 80, 89 ] }
$pullAll

syntax:

{ $pullAll: { <field1>: [ <value1>, <value2> ... ], ... } }

example:

db.survey.drop()
db.survey.insertOne( { _id: 1, scores: [ 0, 2, 5, 5, 1, 0 ] } )

// remove score[score in [0, 5]]
db.survey.updateOne( { _id: 1 }, { $pullAll: { scores: [ 0, 5 ] } } )

// result
{ "_id" : 1, "scores" : [ 2, 1 ] }

Array modifiers:

Name 説明 原文
$each $pushで使う場合と$addToSetで使う場合がある。複数の要素を追加する機能。 Modifies the $push and $addToSet operators to append multiple items for array updates.
$position $push操作を変更する。追加位置を変更する機能。 Modifies the $push operator to specify the position in the array to add elements.
$slice $push操作を変更する。更新される配列のサイズを制限する機能。※追記:$eachを伴わなければならない。追加後にサイズを超えた分は削除される。指定値がマイナスなら末尾から、プラスなら先頭から数えた分を残す。 Modifies the $push operator to limit the size of updated arrays.
$sort $push操作を変更する。配列を並び替える。 Modifies the $push operator to reorder documents stored in an array.
$push.$each:

syntax:

{ $push: { <field>: { $each: [ <value1>, <value2> ... ] } } }

example Use $each with $push Operator:

db.students.insertOne({name:"joe"})
db.students.updateOne(
   { name: "joe" },
   { $push: { scores: { $each: [ 90, 92, 85 ] } } }
)
db.students.find( { name:'joe'  } )

// result 1
[
  {
    _id: ObjectId(...),
    name: 'joe',
    scores: [ 90, 92, 85 ]
  }
]

db.students.updateOne(
   { name: "joe" },
   { $push: { scores: { $each: [ 90, 92, 85 ] } } }
)
db.students.find( { name:'joe'  } )

// result 2
[
  {
    _id: ObjectId(...),
    name: 'joe',
    scores: [ 90, 92, 85, 90, 92, 85 ]
  }
]
$addToSet.$each:

syntax:

{ $addToSet: { <field>: { $each: [ <value1>, <value2> ... ] } } }

example:

db.inventory.insertOne({ _id: 2, item: "cable", tags: [ "electronics", "supplies" ] })
db.inventory.find( { _id: 2 } )

db.inventory.updateOne(
   { _id: 2 },
   { $addToSet: { tags: { $each: [ "camera", "electronics", "accessories" ] } } }
 )
db.inventory.find( { _id: 2 } )

// result
[
  {
    _id: 2,
    item: 'cable',
    tags: [ 'electronics', 'supplies', 'camera', 'accessories' ]
  }
]
$position:

syntax:

{
  $push: {
    <field>: {
       $each: [ <value1>, <value2>, ... ],
       $position: <num>
    }
  }
}

example:

db.students.deleteOne( { _id: 1 } )
db.students.find({_id: 1})

db.students.insertOne( { "_id" : 1, "scores" : [ 100 ] } )
db.students.find({_id: 1})

// push [50, 60, 70] into first index
db.students.updateOne(
   { _id: 1 },
   {
     $push: {
        scores: {
           $each: [ 50, 60, 70 ],
           $position: 0
        }
     }
   }
)
db.students.find({_id: 1})

// result
{ "_id" : 1, "scores" : [  50,  60,  70,  100 ] }
$slice:

https://www.mongodb.com/docs/manual/reference/operator/update/slice/#mongodb-update-up.-slice
syntax:

{
  $push: {
     <field>: {
       $each: [ <value1>, <value2>, ... ],
       $slice: <num>
     }
  }
}

$slice value minus pattern:

// prepare
db.students.drop()
db.students.insertOne({"_id" : 1, "scores" : [ 40, 50, 60 ]})
db.students.find()
// prepared
{ "_id" : 1, "scores" : [ 40, 50, 60 ] }

// $slice - : [ 40, 50, 60 ] -> [ 40, 50, 60, 80, 78, 86 ] -> [ 50, 60, 80, 78, 86 ]
db.students.updateOne(
   { _id: 1 },
   {
     $push: {
       scores: {
         $each: [ 80, 78, 86 ],
         $slice: -5
       }
     }
   }
)
db.students.find()

// result
{ "_id" : 1, "scores" : [  50,  60,  80,  78,  86 ] }

$slice value plus pattern:

// prepare
db.students.drop()
db.students.insertOne({ "_id" : 2, "scores" : [ 89, 90 ] })
db.students.find()
// prepared
{ "_id" : 2, "scores" : [ 89, 90 ] }

// $slice + : [89, 98] -> [89, 98, 100, 20] -> [89, 98, 100]
db.students.updateOne(
   { _id: 2 },
   {
     $push: {
       scores: {
         $each: [ 100, 20 ],
         $slice: 3
       }
     }
   }
)
db.students.find()

// result
{ "_id" : 2, "scores" : [  89,  90,  100 ] }
$sort:

https://www.mongodb.com/docs/manual/reference/operator/update/sort/#mongodb-update-up.-sort
syntax:

{
  $push: {
     <field>: {
       $each: [ <value1>, <value2>, ... ],
       $sort: <sort specification>
     }
  }
}

ドキュメント要素で構成された配列をドキュメント要素の属性で昇順ソート
ascending sort array of documents by a field in documents:

// prepare
db.students.drop()
db.students.insertOne(
   {
     "_id": 1,
     "quizzes": [
       { "id" : 1, "score" : 6 },
       { "id" : 2, "score" : 9 }
     ]
   }
)
db.students.find()
// prepared
{ _id: 1, quizzes: [ { id: 1, score: 6 }, { id: 2, score: 9 } ] }

// [{score:6}, {score:9}]
// -> [{score:6}, {score:9}, {score:8}, {score:7}, {score:6}]
// -> [{score:6}, {score:6}, {score:7}, {score:8}, {score:9}]
db.students.updateOne(
   { _id: 1 },
   {
     $push: {
       quizzes: {
         $each: [{id:3, score:8}, {id: 4, score:7}, {id:5, score:6}],
         $sort: { score: 1 }
       }
     }
   }
)
db.students.find()
{
  "_id" : 1,
  "quizzes" : [
     { "id" : 1, "score" : 6 },
     { "id" : 5, "score" : 6 },
     { "id" : 4, "score" : 7 },
     { "id" : 3, "score" : 8 },
     { "id" : 2, "score" : 9 }
  ]
}

配列属性を昇順ソート
ascending sort array elements that are not documents:

// prepare
db.students.drop()
db.students.insertOne( { "_id" : 2, "tests" : [  89,  70,  89,  50 ] } )
db.students.find()

// [ 89, 70, 89, 50 ]
// -> [ 89, 70, 89, 50, 40, 60 ]
// -> [ 40, 50, 60, 70, 89, 89 ]
db.students.updateOne(
   { _id: 2 },
   { $push: { tests: { $each: [ 40, 60 ], $sort: 1 } } }
)
db.students.find()

// result
{ "_id" : 2, "tests" : [  40,  50,  60,  70,  89,  89 ] }

descending sort without push data:

// prepare
db.students.drop()
db.students.insertOne( { "_id" : 3, "tests" : [  89,  70,  100,  20 ] } )
db.students.find()

// [ 89, 70, 100, 20 ] -> [ 100, 89, 70, 20 ]
db.students.updateOne(
   { _id: 3 },
   { $push: { tests: { $each: [ ], $sort: -1 } } }
)
db.students.find()

// result
{ "_id" : 3, "tests" : [ 100,  89,  70,  20 ] }

Bitwise:

Name 説明 原文
$bit 整数値にビット演算を行う Performs bitwise AND, OR, and XOR updates of integer values.
$bit example:

https://www.mongodb.com/docs/manual/reference/operator/update/bit/#mongodb-update-up.-bit
syntax:

{ $bit: { <field>: { <and|or|xor>: <int> } } }

target data:

db.switches.drop()
db.switches.insertMany( [
   { _id: 1, expdata: Int32(13) },
   { _id: 2, expdata: Int32(3) },
   { _id: 3, expdata: Int32(1) }
] )
db.switches.find()

// result
[
  { _id: 1, expdata: 13 },
  { _id: 2, expdata: 3 },
  { _id: 3, expdata: 1 }
]

AND:

db.switches.find( { _id: 1 } )
{ _id: 1, expdata: 13 }

// 13 & 10 = 1101 & 1010 = 1000 = 8
db.switches.updateOne(
   { _id: 1 },
   { $bit: { expdata: { and: Int32( 10 ) } } }
)
db.switches.find( { _id: 1 } )

// result
{ "_id" : 1, "expdata" : 8 }
1101 //13
1010 //10
----
1000 // 8

OR:

db.switches.find({ _id: 2 })
[ { _id: 2, expdata: 3 } ]

// 3 | 5 = 0011 | 0101 = 0111 = 7
db.switches.updateOne(
   { _id: 2 },
   { $bit: { expdata: { or: Int32( 5 ) } } }
)
db.switches.find({ _id: 2 })

// result
{ "_id" : 2, "expdata" : 7 }
0011 // 3
0101 // 5
----
0111 // 7

XOR:

db.switches.find({ _id: 3 })
[ { _id: 3, expdata: 1 } ]

// 1 ^ 5 = 0001 ^ 0101 = 0100 = 4
db.switches.updateOne(
   { _id: 3 },
   { $bit: { expdata: { xor: Int32( 5 ) } } }
)
db.switches.find({ _id: 3 })

// result
{ "_id" : 3, "expdata" : 4 }
0001 // 1
0101 // 5
----
0100 // 4

Delete 削除

  • db.collection.deleteMany()
    To delete all documents from the sample_mflix.movies collection:

    use sample_mflix
    db.movies.find()
    db.movies.deleteMany({})
    db.movies.find()
    

    ※moviesのデータがすべて消えるのでDelete 削除.recovery内のコマンドでデータを復旧する。

    To delete all documents from the sample_mflix.movies collection where the title equals "Titanic":

    use sample_mflix
    db.movies.deleteMany( { title: "Titanic" } )
    
  • db.collection.deleteOne()
    To delete the first document from the sample_mflix.movies collection where the cast array contains "Sarah Clarke":

    use sample_mflix
    db.movies.find( { cast: "Sarah Clarke" } )
    db.movies.deleteOne( { cast: "Sarah Clarke" } )
    db.movies.find( { cast: "Sarah Clarke" } )
    

recovery:

削除後のデータ復旧用コマンドを下記に記載。

この記事内で利用しているデータの復旧コマンド
use sample_mflix
db.movies.drop()
db.movies.insertMany([
  {
    title: 'The Favourite',
    genres: [ 'Drama', 'History' ],
    runtime: 121,
    rated: 'R',
    year: 2018,
    directors: [ 'Yorgos Lanthimos' ],
    cast: [ 'Olivia Colman', 'Emma Stone', 'Rachel Weisz' ],
    type: 'movie'
  },
  {
    title: 'Jurassic World: Fallen Kingdom',
    genres: [ 'Action', 'Sci-Fi' ],
    runtime: 130,
    rated: 'PG-13',
    year: 2018,
    directors: [ 'J. A. Bayona' ],
    cast: [ 'Chris Pratt', 'Bryce Dallas Howard', 'Rafe Spall' ],
    type: 'movie'
  },
  {
    title: 'Tag',
    genres: [ 'Comedy', 'Action' ],
    runtime: 105,
    rated: 'R',
    year: 2018,
    directors: [ 'Jeff Tomsic' ],
    cast: [ 'Annabelle Wallis', 'Jeremy Renner', 'Jon Hamm' ],
    type: 'movie'
  },
  {
    plot: 'A teenage girl risks everything when she falls in love with a vampire.',
    genres: [ 'Drama', 'Fantasy', 'Romance' ],
    rated: 'PG-13',
    cast: [
      'Kristen Stewart',
      'Sarah Clarke',
      'Matt Bushell',
      'Billy Burke'
    ],
    title: 'Twilight'
  },
  {
    title: 'Tony',
    genres: [ 'Comedy' ],
    year: 2023,
    rated: 'R18'
  },
  {
    plot: 'The story of the 1912 sinking of the largest luxury liner ever built, the tragedy that befell over two thousand of the rich and famous as well as of the poor and unknown passengers aboard the doomed ship.',
    genres: [ 'Action', 'Drama', 'History' ],
    cast: [
      'Peter Gallagher',
      'George C. Scott',
      'Catherine Zeta-Jones',
      'Eva Marie Saint'
    ],
    poster: 'https://m.media-amazon.com/images/M/MV5BYWM0MDE3OWMtMzlhZC00YzMyLThiNjItNzFhNGVhYzQ1YWM5XkEyXkFqcGdeQXVyMTczNjQwOTY@._V1_SY1000_SX677_AL_.jpg',
    title: 'Titanic',
    fullplot: "The plot focuses on the romances of two couples upon the doomed ship's maiden voyage. Isabella Paradine (Catherine Zeta-Jones) is a wealthy woman mourning the loss of her aunt, who reignites a romance with former flame Wynn Park (Peter Gallagher). Meanwhile, a charming ne'er-do-well named Jamie Perse (Mike Doyle) steals a ticket for the ship, and falls for a sweet innocent Irish girl on board. But their romance is threatened by the villainous Simon Doonan (Tim Curry), who has discovered about the ticket and makes Jamie his unwilling accomplice, as well as having sinister plans for the girl.",
    languages: [ 'English' ],
    released: { '$date': { '$numberLong': '848188800000' } },
    rated: 'PG-13',
    awards: {
      wins: { '$numberInt': '0' },
      nominations: { '$numberInt': '9' },
      text: 'Won 1 Primetime Emmy. Another 8 nominations.'
    },
    lastupdated: '2015-08-30 00:47:02.163000000',
    year: { '$numberInt': '1996' },
    countries: [ 'Canada', 'USA' ],
    type: 'series'
  }
])

終わりに

mongoshのCRUDの操作の例を追うことでどのような機能が用意されていて、どう使うのかがわかったように思う。仮に忘れても記事を読み返すことで使うべき操作を探すことができるようにも思える。しかし改めてUdemyの講座に出てくるコードを見てみるとSchemaのようなDBとNode.js/expressを繋ぐ部分についてもう少し調べたほうがいいかもしれないと感じる部分が出てきた。知識が増えると疑問も増える。しかし時間は無限ではないので割り切って進めるべきでは?という焦燥感も出てくる。どうせ着手しても知らないことが出てきて手が止まるのだけど。

脚注
  1. https://mongoosejs.com/docs/search.html?q=%24push ↩︎

  2. https://mongoosejs.com/docs/search.html?q=%24set ↩︎

  3. https://www.mongodb.com/docs/mongodb-shell/#mongodb-binary-bin.mongosh ↩︎

  4. https://www.mongodb.com/docs/manual/core/databases-and-collections/#databases-and-collections ↩︎

  5. https://www.mongodb.com/docs/manual/introduction/#document-database ↩︎

Discussion