MongoDB增删改查

温馨提示:点击页面下方以展开或折叠目录

摘要:MongoDB学习过程中对CRUD用法的记录,毕竟好记性不如烂笔头。

文章说明
文章作者:鴻塵
文章链接:https://hwame.top/20210716/mongodb-crud-operations.html
参考资料:MongoDB CRUD Operations: Version 5.0 latest

1.概述

1.1.写作的动机

由于工作需要,捡起了遗忘多时的MongoDB,项目中使用的是4.2版本,恰好本地电脑上多年前装的也是4.2版本,如今官方文档都已经更新到5.0了。

相比旧版本,5.0增加了一些新功能,比如聚合里的一些stage,4.2版本算是一个比较经典的版本吧(就好比mysql的5.7和8.0)。

决定记录的另一个动机,则是网上的教程不够全面,官方文档也没有中文版的,有些翻译很生硬且不准确:

在比较之后遂决定啃最新的5.0官方文档,怎么说呢,任何问题都可以从中找到答案。

MongoDB提供了一个在线版的Mongo shell,默认为latest即v5.0,可以通过参数选择版本,例如https://mws.mongodb.com/?version=4.2
唯一的缺点就是连接维持的时间比较短,一不留神就断开了,重连会丢失之前的数据。

1.2.啃文档的一些感受

这篇文章写的是CRUD,是相对比较基础的部分,目前在看管道聚合,准备写下一篇。

在看过几天文档(尤其是管道聚合部分)后有一些感受,主要来自管道聚合,但是估计下一篇内容会很多,这篇内容较少就放在这里吧。主要有以下几点:

  • ①官方文档写的很细,例如Aggregation Pipeline StagesAggregation Pipeline Operators都有名为$count累积器accumulator管道阶段stageAggregation Pipeline Operators里有分别名为$first$last的同名累积器accumulator操作符operator。文档不厌其烦地在每个出现的地方都有「消除歧义Disambiguation」的提示,但这也导致了文档比较冗杂。
  • ②官方文档内容比较混乱,从路由上就可以看出来。比如Aggregation Pipeline StagesAggregation Pipeline Operators路由的命名分别为aggregation-pipelineaggregation,但是这两个对应的类似$xxx的格式却都是https://docs.mongodb.com/manual/reference/operator/aggregation/xxx/,这难免让人摸不着头脑。为何不将路由命名为aggregation-pipeline-stagesaggregation-pipeline-operators,且将对应$xxx放到各自路径下呢?
  • ③官方文档内容分类不够具体,如果能按分类添加下级路由,那么文档的结构和层次会更清楚了。如下两例:
    • 例如,Aggregation Pipeline Stages可分为「db.collection.aggregate()Stages」、「db.aggregate()Stages」和「Stages Available for Updates」三个部分,完全可以添加三个子路由啊。
    • 还有,Aggregation Pipeline Operators操作表达式分为「算术表达式操作符」、「数组表达式操作符」、「布尔表达式操作符」、「比较表达式操作符」、「条件表达式操作符」、「自定义聚合表达式操作符」、「数据尺寸操作符」、「日期表达式操作符」、「字面表达式操作符」、「杂项操作符」、「对象表达式操作符」、「set表达式操作符」、「字符串表达式操作符」、「文本表达式操作符」、「三角函数表达式操作符」、「类型表达式操作符」、「$group阶段的累积器」、「$group阶段的累积器」、「变量表达式操作符」和「窗口操作符」。单单是分类就已经有20个之多,内容那就更多了。如果按分类添加20个子路由,文档的结构和层次会更清楚了。
    • 当然,文档很贴心地在Aggregation Pipeline StagesAggregation Pipeline Operators最后添加了按字母排序的列表(Alphabetical Listing)以供索引查阅。需要说明的是,各分类并不是互斥的,各类里面有重叠的项,大概这就是官方不予添加子路由的原因吧。
  • ④相近的概念没有辨析清楚【这里应该与翻译和表达有关,不全是官方的锅】,比如「管道聚合」里的stage和operation是并列的,operator用于stage/operation里,中文里「操作」一词即可作名词也可做动词,翻译阅读起来就会产生歧义,所以我把stage和operation统一当成「阶段」,operator则称为「操作符」或「操作表达式」。从这个层面上说,官方文档的描述还是很准确的,只不过全是长难句一连串的定语有时候都分不清修饰的到底是谁,引起理解上的歧义。

我们常说的CRUD即是「增删改查」,具体说来:

  • CCreate,增,即创建操作;
  • RRead ,查,即查询操作;
  • UUpdate,改,即更新操作;
  • DDelete,删,即删除操作。

2.创建操作

创建/插入是针对单个集合而言,「写操作」在单个文档级别上具有「原子性」。如果当前集合不存在,则插入操作将创建该集合。

1
2
3
4
# 插入单个文档是「{}」,多个是「[{}, {}, ...]」
db.collection.insertOne() # 单个
db.collection.insertMany() # 多个
db.collection.insert() # 单个或多个

upsert: true 选项 一起使用时也可实现插入:

  • db.collection.update()db.collection.updateOne()db.collection.updateMany()
  • db.collection.findAndModify()db.collection.findOneAndUpdate()db.collection.findOneAndReplace()
  • db.collection.bulkWrite()Performs multiple write operations with controls for order of execution

3.查询操作

查询操作语法为db.collection.find(query, projection),其中query指定查询条件,projection指定返回的字段。

以下示例使用inventory集合,包含如下数据:

1
2
3
4
5
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" }
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" }
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" }
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" }
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }

  1. 选择集合中的所有文档:query={}query=(留空不写),相当于SELECT * FROM inventory
  2. 指定相等条件:query={<field1>: <valve1>, ...},例如{status: "D"}相当于SELECT * FROM inventory WHERE status = "D"
  3. 使用查询运算符(Query Operators)指定条件:{<field1>: {<operator1>: <value1>}, ...},例如{status: {$in: ["A", "D"]}}相当于SELECT * FROM inventory WHERE status in ("A", "D")【此处也可用$or,但对同一字段应该用$in而非$or】。
  4. 指定AND条件:{<and1>: <value1>, <and2>: <value2>, ...},例如{status: "A", qty: {$lt: 30}}相当于SELECT * FRON inventory WHERE status = "A" AND qty < 30
  5. 指定OR条件:{$or: [<query1>, <query2>, ...]},例如{$or: [{status: “A"}, {qty: {$lt: 30}}]}相当于SELECT * FROM inventory WHERE status = "A" OR qty < 30
  6. 指定AND以及OR条件【即 4+5 组含】:<and>: <value>, $or: [<query1>, <query2>, ...],例如{status: "A", $or: [qty: {$lt: 30}}, {item: /^p/}]}相当于SELECT * FROM inventory WHERE status = "A" AND (qty < 30 OR item LIKE "p%")【支持正则表达式】。

3.1.查询嵌入/嵌套文档

  • 嵌套文档匹配即<value = document>,格式为:query={<field1>: <doc1>, ...},例如{size: {h: 14, w: 21,uom: "cm"}}注意: 整个嵌入文档的相等匹配需要精确匹配,包括顺序!
  • 嵌套字段匹配即嵌套文挡中字殿的匹配,字段使用点表示法:field.subField
    • 相等匹配:{<field.subField>: <value>, ...},例如{"size.uom": "in"}
    • 查询运算符:{<field1>: {<operator1>: <value1>}, ...},例如{"size.h": {$lt: 15}},与 3. 相同;
    • 指定AND条件:{<field1.subField1>: <valuel>, ...},例加{"size.h": {$lt: 15}, "size.uom": "in", states: "D"},与 4. 相同。

3.2.查询数组

  • 匹配一个数组,即value = array,精确匹配包括顺序。
    • 例如{tags: ["red", "black"]}只匹配包含2个元素的定序的["red", "black"]
    • 若要匹配无序的包含此2元素的数组(len ≥ 2),则需使用$all运算符:{tags: {$all: ["red", "black"]}}
  • 查询一个元素的数组(包含指定元素的数组),即arrField = arrValue
    • 例如{tags: "red"}匹配"red" in array(tags)
    • 可以对arrValue指定条件过滤器,即{arrField1: {operator1: value1, ...}},例如{dim_cm: {$gt: 25}}匹配any(dim_cm[i] > 25),即dim_cm中存在大于25的元素。
  • 为数组元素指定多个条件(复合条件),使 单个数组元素 满足这些条件或 数组元素的任意组合 满足条件。
    • 在数组元素上使用复合过滤条件查询数组:数组中存在满足条件的元素(这些元素同时存在于该数组即可,无需是同一个元素),例如{dim_cm: {Sgt: 15, slt: 20}}表示dim_cm[i] > 15 AND dim_cm[j] < 2015 < dim_cm[kJ < 20
    • 查询满足多个条件的数组元素:一个元素同时满足条件,例如{dim_cm: {$elemMatch: {Sgt: 15, $lt: 20}}}表示15 < dim_cm[i] < 20
    • 通过数组索引位置查询元素:点表示法(dot notation)可以为指定位置的元素设置条件,索引从0开始【 字段和嵌套字段必须在引号内 】,例如{"dim_cm.1": {$gt: 25}}表示dim_cm[1] > 25
    • 按数组长度查询数组{"tags": {$size: 3}}表示len(tags) = 3

3.3.查询嵌套文档数组

嵌套文档数组(Array of Embedded Documents)即以文档为元素的数组,集合inventory文档格式为{item: "xx", instock: [{warehouse: "A", qty: 5}, {}...]}

  • 查询嵌套在数组中的文档:{field: doc}doc in array(field)(查询数组2:包含指定元素的数组),doc精确匹配包括顺序。例如{"instock": {warehouse: "A", qty: 5}}匹配的数组instock包含整个「有序」文档{warehouse: "A", qty: 5}
  • 在文档数组中的字段上指定查询条件【点表示法 字段和嵌套字段必须在引号内 】。
    • 对嵌入在文档数组中的字段指定查询条件:例如{'instock.qty': {$lte: 20}}匹配数组元素instock[i] = doc,doc.qty ≤ 20,不检查doc位置;
    • 使用数组索引查询嵌入文档中的字段: 例如{'instock.0.qty': {$lte: 20}}匹配数组元素instock[0] = doc,doc.qty ≤ 20,指定doc位置为0,因此结果是上一个的 子集
  • 为文档数组指定多个条件,使得数组中的文档或文档组合满足条件。
    • 单个嵌套文档在嵌套字段上满足多个查询条件,例如{"instock": {$elemMatch: {qty: 5, warehouse: "A"}}}匹配instock[i]=doc,{qty: 5, warehouse: "A"} in doc,此处doc包含这两值即可且无序。再如{"instock": {$elemMatch: {qty: {$gt: 10, $lte: 20}}}}匹配instock[i]=doc,10 < doc.qty < 20
    • 元素组合满足条件, 若数组字段上的复合查询条件不使用$elemMatch运算行,则匹配「文档组合满足条件的数组」,注意是 文档组合 满足而非 文档 满足。例如{"instock.qty": {$gt: 10, $lte: 20}}匹配instock[i].qty > 10,instock[j].qty < 20【上一个是单个文档同时满足,使用了$elemMatch】。类似的,{"instock.qty": 5, "instock.warehouse": "A"}将匹配instock[i].qty = 5,instock[j].warehouse = "A"而不要求位于同一文档中。

3.4.从查询返回指定字段

从查询返回指定字段即投影(Projection),查询语法db.inventory.find(query, projection),其中projection.field = (1=true | 0=false)
查询默认返回所有字段【相当于SELECT *】,使用投影(Projection)来指定或限制返回的字段【相当于SELECT field1, field2...】。

  • 返回匹配文档中的所有字段:不指定projection相当于SELECT *
  • 仅返回指定字段和_idprojection = {field1: 1, field2: 1},默认会返回_id字段,相当于SELECT _id, field1, field2
  • 禁用_id字段,通过 显式地置0 从结果中删除_id字段:projection = {field1: 1, field2: 1, _id: 0}
  • 返回除排除字段之外的所有字段(补集思想),通过置0来排除字段,返回剩下的字段。 注意: 除了_id字段以外,不能在projection文档中对「包含和排除语句」进行组合,即不能同时出现0和1。
  • 返回/禁用嵌套文档中的特定字段:使用点表示法指定嵌套文档中的特定字段,如projection = {item: 1, status: 1, "size.uom": 1}projection = {"size.uom": 0}
  • 数组中嵌入文档的投影,使用点表示法在嵌入数组的文档中投影特定字段,例如projection = {item: 1, status: 1, "instock.qty": 1}的查询结果中instock数组元素(即doc文档)只有qty字段。
  • 在返回的数组中投影指定数组元素:对于包含了数组的字段,投影算子$elemMatch$slice$用来操作数组。例如projection = {item: 1, status: 1, instock: {$slice: -1}} 表示只取instock数组最后一个元素。

    上述3个投影算子是「从返回数组中」投影「指定元素」的 唯一 方法。也就是说,不能使用素引{"instock.0": 1},将报错SyntaxError expected property name, got '{'

3.5.查询空字段或缺失字段

MongoDB中的不同查询运算符对null值的处理方式不同。查询语法db.inventory.find(query, projection),示例集合inventory为:

1
2
{ _id: 1, item: null}
{ _id: 2}

  • 相等过滤器:query = {item: null}查询匹配item = null OR !item,结果返回所有的两个文档。
  • 类型检查:query = {item: {$type: 10}}查询匹配item = null即字段item的值为「BSON类型值Null,其类型编号为10」,结果返回_id = 1的文档。
  • 存在检查,$exists检查是否存在指定字段:query = {item: {$exists: false}}查询匹配!itemexists(item) = false,结果返回_id = 2的文档(不包含item字段)。

4.更新操作

更新操作用于修改集合中已有的文档,在单个文档级别上具有「原子性」,(与「增」相同)
可以指定条件或过滤器,来进行更新:

1
2
3
db.collection.updateOne(<filter>, <update>, <options>)
db.collection.updateMany(<filter>, <update>, <options>)
db.collection.replaceOne(<filter>, <update>, <options>)

利用更新操作符$set来修改字段值,「更新方法」的格式:
注意:如果字段不存在,某些更新运算符(例如$set)将创建该字段。
1
2
3
4
{
<update_operator1>: {<field1>: <value1>, ...},
<update_operator2>: {<field2>: <value2>, ...},
}

例1:更新单个文档

例如原文档:{item: "paper", qty: 100, size: {h: 8.5, w: 11, uom: "in"}, status: "D"}
更新后文档:{item: "paper", qty: 100, size: {h: 8.5, w: 11, uom: "cm"}, status: "P", lastModified: ISODate("2021-07-21T02:40:43.515Z")}
更新操作:

  • 使用$set修改"size.uom" = "cm"status = "p"
  • 使用$currentDatelastModified更新为当前日期,不存在的字段将被创建。
1
2
3
4
5
6
7
db.inventory.updateOne(
{item: "paper"},
{
$set: {"size.uom": "cm", status: "P"},
$currentDate: {lastModified: true}
}
)

例2:更新多个文档

1
2
3
4
5
6
7
8
9
# 例如原文档
{item: "journal", qty: 25, size: {h: 14, w: 21, uom: "cm"}, status: "A"},
{item: "mousepad", qty: 25, size: {h: 19, w: 22.85, uom: "cm"}, status: "P"},
{item: "postcard", qty: 45, size: {h: 10, w: 15.25, uom: "cm"}, status: "A"}

# 更新后文档
{item: "journal", qty: 25, size: {h: 14, w: 21, uom: "in"}, status: "P", lastModified: ISODate("2021-07-21T02:56:56.020Z")},
{item: "mousepad", qty: 25, size: {h: 19, w: 22.85, uom: "in"}, status: "P", lastModified: ISODate("2021-07-21T02:56:56.020Z")},
{item: "postcard", qty: 45, size: {h: 10, W: 15.25, uom: "in"}, status: "P", lastModified: ISODate("2021-07-21T02:56:56.020Z")}

更新操作:

  • 更新满足qty < 50的所有文档(例1中qty = 100);
  • 使用$set修改"size.uom" = "in"status = "p"
  • 使用$currentDatelastModified更新为当前日期,不存在的字段将被创建。
1
2
3
4
5
6
7
db.inventory.updateMany(
{"qty": {$lt: 50}},
{
$set: {"size.uom": "in", status: "P"},
$currentDate: {LastModified: true}
}
)

例3:替换单个文档

例如文档的更新情况:

  • 原始示例文档:{item: "paper", qty: 100, size: {h: 8.5, w: 11, uom: "in"}, status: "D"}
  • 例1修改后文档:{item: "paper", qty: 100, size: {h: 8.5, w: 11, uom: "cm"}, status: "P", lastModified: ISODate("2021-07-21T02:40:43.515Z")}
  • 例3修改后文档:{item: "paper", instock: [{warehouse: "A", qty: 60}, {warehouse: "B", qty: 40}]}

更新操作,直接修改整个item字段为paper的文档:

1
2
3
4
db.inventory.replaceOne(
{item: "paper"},
{item: "paper", instock: [{warehouse: "A", qty: 60}, {warehouse: "B", qty: 40}]}
)

使用聚合管道更新

从MongoDB 4.2开始,更新操作可以使用聚合管道(Aggregation Pipeline),聚合管道可以由以下stage组成:$addFields$set$project$unset$replaceRoot$repaceWith

使用聚合管道允许更具表现力的update语句,例如基于当前字段值来表示条件更新,或者使用另一个字段的值更新一个字段。

  • 示例1。db.students.updateOne({_id: 3}, [{$set: {"test3": 98,modified: "$$NOW"}}])更新文档_id: 3,其中$setstage将创建不存在的字段test3 = 98,并将字段modified更新为当前时间。该操作使用「聚合变量(aggregation variable)NOW获取当前时间,使用「双dollar符前缀」并「加引号」来访问变量:"$$NOW"
  • 示例2。使用聚合管道来标准化文档中的字段,即集合中的文档应具有相同的字段,同时更新modified字段。其中:
    • 带有$mergeObjects表达式的$replaceRootstage用来为quiz1quiz2test1test2字段设置默认值,聚合变量ROOT指的是当前正在修改的文档。当前文档字段将覆盖默认值。
    • $setstage将modified字段更新为当前时间。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      db.students2.insertMany([
      {"_id": 1, quiz1: 8, test2: 100, quiz2: 9, modified: new Date("01/05/2020")},
      {"_id": 2, quiz2: 5, test1: 80, test2: 89, modified: new Date("01/05/2020")},
      ])

      db.students2.updateMany({},
      [
      {$replaceRoot: {newRoot:
      {$mergeObjects: [{quiz1: 0, quiz2: 0, test1: 0, test2: 0}, "$$ROOT"]}
      }
      },
      {$set: {modified: "$$NOW"}}
      ]
      )

      # 结果:
      # {_id: 1, quiz1: 8, quiz2: 9, test1: 0, test2: 100, modified: ISODate("2021-07-21T04:03:43.185Z")},
      # {_id: 2, quiz1: 0, quiz2: 5, test1: 80, test2: 89, modified: ISODate("2021-07-21T04:03:43.185Z")}
  • 示例3。使用聚合管道来计算「平均分数和成绩等级」,同时更新modified字段。其中:
    • 第一个$setstage用来①计算tests数组的平均值({$avg: "$tests"})并截断取整({$trunc: [<number>, 0]}),将取整的值赋给average并创建;②将modified字段更新为当前时间。【\$trunc用法
    • 第二个$setstage根据上一步的average,使用$switch表达式创建grade。【\$switch用法
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      db.students3.insert([
      {"_id": 1, "tests": [95, 92, 98], "modified": ISODate("2019-01-01T00:00:00Z")},
      {"_id": 2, "tests": [94, 88, 90], "modified": ISODate("2019-01-01T00:00:00Z")},
      {"_id": 3, "tests": [70, 75, 82], "modified": ISODate("2019-01-01T00:00:00Z")}
      ]);

      db.students3.updateMany({},
      [
      {$set: {average: {$trunc [{$avg: "$tests"}, 0]}, modified: "$$NOW"}},
      {$set: {grade: {$switch: {
      branches: [
      {case: {$gte: ["$average", 90]}, then: "A"},
      {case: {$gte: ["$average", 80]}, then: "B"},
      {case: {$gte: ["$average", 70]}, then: "C"},
      {case: {$gte: ["$average", 60]}, then: "D"}
      ],
      default: "F"
      }}}}
      ]
      )

      # 结果:
      # {"_id": 1, "tests": [95, 92, 90], "modified": ISODate("2021-07-21T06:06:50.533Z"), "average": 92, "grade": "A"},
      # {"_id": 2, "tests": [94, 88, 90], "modified": ISODate("2021-07-21T06:06:50.533Z"), "average": 90, "grade": "A"},
      # {"_id": 3, "tests": [70, 75, 82], "modified": ISODate("2021-07-21T06:06:50.533Z"), "average": 75, "grade": "C"},
  • 示例4。使用聚合管道进行array的拼接:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    db.students4.insertMany([
    {"_id": 1, "quizzes": [4, 6, 7]},
    {"_id": 2, "quizzes": [5]},
    {"_id": 3, "quizzes": [10, 10, 10]}
    ])

    db.students4.updateOne({_id: 2},
    [
    {$set: {quizzes: {$concatArrays: ["$quizzes", [8, 6]]}}}
    ]
    )

    # 结果:
    # {"_id": 1, "quizzes": [4, 6, 7]},
    # {"_id": 2, "quizzes": [5, 8, 6]},
    # {"_id": 3, "quizzes": [10, 10, 10]}
  • 示例5。使用聚合管道将「摄氏温度」转换为「华氏温度」,其中:
    • 管道包含$addFieldsstage(与$set等价),用以创建新的数组字段tempsF(包含华氏温度的数组);
    • 该stage使用$map``$add$multiply表达式来进行温度转换$F=C\times \frac{9}{5}+32$。【$map用法,对len(tempsC)无要求】
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      db.temperatures.insertMany([
      {"_id": 1, "date": ISODate("2019-06-23"), "tempsC": [ 4, 12, 17]},
      {"_id": 2, "date": ISODate("2019-07-07"), "tempsC": [14, 24, 11]},
      {"_id": 3, "date": ISODate("2019-10-30"), "tempsC": [18, 6, 8]}
      ])

      db.temperatures.updateMany({},
      [
      {$addFields: {"tempsF": {
      $map: {
      input: "$tempsC",
      as: "celsius",
      in: {$add: [{$multiply: ["$$celsius", 9/5]}, 32]}
      }
      }}}
      ]
      )

      # 结果:
      # {"_id": 1, "date": ISODate("2019-06-23T00:00:00.000Z"), "tempsC": [ 4, 12, 17], "tempsF": [39.2, 53.6, 62.6]},
      # {"_id": 2, "date": ISODate("2019-07-07T00:00:00.000Z"), "tempsC": [14, 24, 11], "tempsF": [57.2, 75.2, 51.8]},
      # {"_id": 3, "date": ISODate("2019-10-30T00:00:00.000Z"), "tempsC": [18, 6, 8], "tempsF": [64.4, 42.8, 46.4]}

update方法

1
2
3
4
5
6
7
8
db.collection.updateOne()
db.collection.updateMany()
db.collection.replaceOne()
db.collection.update()
db.collection.findOneAndReplace()
db.collection.findOneAndUpdate()
db.collection.findAndModify()
db.collection.bulkWrite()

5.删除操作

从集合中删除文档,具有「原子性」,也可指定条件或过滤器:

1
2
db.collection.deleteOne(filter)
db.collection.deleteMany(filter)

  • 删除所有文档。将过滤器文档置空:db.inventory.deleteMany({})
  • 删除所有符合条件的文档。指定用于标识要删除文档的条件或过滤器,过滤器语法与查询操作相同。在「查询过滤器文档(query filter docuent)」中使用field: value表达式来指定相等匹配条件:{<field1>: <value1>, ...},同理,将value替换成「查询操作符」来指定匹配条件:{<field1>: {<operator1>: <value1>}, ...}
  • 只删除一个符合条件的文档。即使多个文档相匹配,也只删除第一个:db.collection.deleteOne(filter)
  • 删除方法:
    • db.collection.deleteOne()
    • db.collection.deleteMany()
    • db.collection.remove()
    • db.collection.findOneAndDelete()
    • db.collection.findAndModify()
    • db.collection.bulkWrite()