MongoDB操作符和管道整理

351 阅读3分钟

查询条件

以单层文档为条件查询时,直接使用key和对应的value查询,同时可以配合操作符。

以多层嵌套文档为条件查询时,此时嵌套为另外一个文档:

在 MongoDB 中筛选多层级文档可以使用点符号 . 来访问嵌套字段。例如,如果有一个多层级文档结构如下:

{
  "_id": 1,
  "name": "John Doe",
  "address": {
    "street": "123 Main St",
    "city": "New York",
    "zipcode": "10001"
  }
}

要筛选其中的某个嵌套字段,可以使用点符号指定路径,例如:

db.collection.find({"address.city": "New York"})

上面的查询会返回具有城市为 "New York" 的地址的文档。

注意:MongoDB 中的查询操作 db.collection.find({"address.": {"city":"New York"}}) 是无效的

以多层嵌套文档为条件查询时,此时嵌套为另外一个数组时:

要查询字段是数组里嵌套文档的情况,可以使用 MongoDB 的 $elemMatch 运算符来进行查询。假设有一个集合中包含一个名为 contacts 的数组字段,数组中的元素是包含 nameemail 字段的文档。现在要查询数组中 email 字段为特定值的文档,可以使用以下方式:

db.collection.find({ contacts: { $elemMatch: { email: "example@email.com" } } })

这样的查询语句会返回数组 contacts 中包含有 email 字段为 "example@email.com" 的文档。注意,$elemMatch 运算符用于匹配数组中至少有一个元素满足指定条件的情况。

如果要进一步筛选数组中满足多个条件的文档,可以在 $elemMatch 运算符中添加多个条件,例如:

db.collection.find({
  contacts: {
    $elemMatch: {
      email: "example@email.com",
      name: "John Doe"
    }
  }
})

这样的查询会返回数组 contacts 中同时满足 email 为 "example@email.com" 和 name 为 "John Doe" 的文档。

常用操作符

MongoDB 提供了丰富的操作符,涵盖了查询、更新、删除等操作。以下是 MongoDB 常用的操作符以及简要的说明和示例:

查询操作符

  1. 比较操作符

    • $eq: 等于
    • $ne: 不等于
    • $gt: 大于
    • $lt: 小于
    • $gte: 大于等于
    • $lte: 小于等于
    db.collection.find({ age: { $gt: 18 } })
    
  2. 逻辑操作符

    • $and: 与
    • $or: 或
    • $not: 非
    db.collection.find({ $or: [{ age: 20 }, { name: "Alice" }] })
    
  3. 元素操作符

    • $exists: 判断字段是否存在
    • $type: 判断字段类型
    db.collection.find({ field: { $exists: true } })
    
  4. 数组操作符

  • $ 作为数组字段的索引:

在 MongoDB 中,如果文档的某个字段是数组类型,你可以使用 array.$来引用数组的某个特定元素。例如,{ "scores.$.score": 100 } 表示更新数组字段 scores 中的某个元素的 score 字段的值为 100。

  • $elemMatch: 匹配数组中满足所有条件的元素
  • $in: 匹配数组中满足任意条件的元素
  • $size: 匹配数组长度
db.collection.find({ tags: { $elemMatch: { $eq: "MongoDB" } } })
  1. 正则表达式操作符
    • $regex: 匹配正则表达式
    db.collection.find({ name: { $regex: "^A" } })
    

更新操作符

  1. 修改器操作符

    • $set: 设置字段值
    • $unset: 删除字段
    • $inc: 字段增加指定值
    db.collection.update({ name: "Alice" }, { $set: { age: 25 } })
    
  2. 数组修改器操作符

    • $push: 添加元素到数组
    • $pull: 从数组中删除元素
    • $addToSet: 添加唯一元素到数组
    db.collection.update({ _id: ObjectId("...") }, { $push: { tags: "MongoDB" } })
    

聚合操作符

  1. 聚合管道内操作符

语法:{[表达式]:$[feild]}, 举例:

db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]);
db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}]);
  1. 算术操作符:
  • $sum 计算总和。
  • $avg 计算平均值
  • $min 获取集合中所有文档对应值得最小值。
  • $max 获取集合中所有文档对应值得最大值。
  • $stdDevPop: 计算数组中数值的总体标准差。
  • $stdDevSamp: 计算数组中数值的样本标准差。
  1. 数组操作符:
  • $push 将值加入一个数组中,不会判断是否有重复的值。
  • $addToSet 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。
  • $first 根据资源文档的排序获取第一个文档数据。
  • $last 根据资源文档的排序获取最后一个文档数据
  • $objectToArray 被用于将文档中的字段(假设这个字段是一个对象)转换为数组,然后通过 $project 阶段将转换后的数组存储到新的字段中。
  • $concatArrays 将多个字段放在一个数组中,请注意,$concatArrays 只能接收数组作为参数
  • $map 操作符用于对数组中的每个元素应用指定的表达式,并返回处理后的新数组。

它的语法结构如下:

{ $map: { input: <array>, as: <string>, in: <expression> } }
- `input`: 指定要处理的数组字段。
- `as`: 定义在表达式中使用的变量名。
- `in`: 定义要对数组中每个元素应用的表达式。

下面是一个简单示例,展示了如何使用 $map 操作符:

假设我们有一个文档如下:

{
  "_id": 1,
  "grades": [80, 85, 90, 95]
}

我们想要将 grades 数组中的每个元素加上 5 分,可以使用 $map 操作符:

db.scores.aggregate([
  {
    $project: {
      adjustedGrades: {
        $map: {
          input: "$grades",
          as: "grade",
          in: { $add: ["$$grade", 5] }
        }
      }
    }
  }
])

在这个示例中,$map 操作符将 grades 数组中的每个元素加上 5 分,然后将处理后的新数组存储在 adjustedGrades 字段中。

  1. 类型转换操作符:
  • $toInt: 将值转换为整数。
  • $toDouble: 将值转换为双精度浮点数。
  • $toBool: 将值转换为布尔类型。
  1. 文本搜索操作符:
  • $text: 在文本字段上执行全文搜索。
  1. 日期操作符:
  • $year: 提取日期的年份。
  • $month: 提取日期的月份。
  • $dayOfMonth: 提取日期的日份。
  • $hour: 提取时间的小时。
  • $minute: 提取时间的分钟。
  • $second: 提取时间的秒数。
  1. 文档运算符:

-$mergeObjects 是一个聚合操作符,用于合并多个文档或字段到一个单一的文档中。 它可以在聚合管道的多个阶段使用,特别是在 $project$addFields$group 等阶段。

以下是一些使用 $mergeObjects 的常见场景和示例:

示例1:基本用法

假设我们有一个集合 orders,其中每个文档包含客户信息和订单信息,如下所示:

{
  "_id": 1,
  "customer": { "firstName": "John", "lastName": "Doe" },
  "order": { "item": "Laptop", "price": 1000 }
}

我们想要将 customerorder 字段合并成一个新的字段 merged。可以使用 $mergeObjects 操作符来完成这一任务:

db.orders.aggregate([
  {
    $project: {
      _id: 0,
      merged: { $mergeObjects: ["$customer", "$order"] }
    }
  }
])

输出结果将是:

{
  "merged": {
    "firstName": "John",
    "lastName": "Doe",
    "item": "Laptop",
    "price": 1000
  }
}

示例2:在数组中使用 $mergeObjects

假设我们有一个文档,其中包含一个嵌套数组字段,每个元素都是一个对象,我们想将这些对象合并成一个单一的对象。示例如下:

{
  "_id": 1,
  "attributes": [
    { "color": "red" },
    { "size": "M" },
    { "price": 29.99 }
  ]
}

我们可以使用 $reduce$mergeObjects 来合并这些对象:

db.collection.aggregate([
  {
    $project: {
      _id: 0,
      mergedAttributes: {
        $reduce: {
          input: "$attributes",
          initialValue: {},
          in: { $mergeObjects: ["$$value", "$$this"] }
        }
      }
    }
  }
])

输出结果将是:

{
  "mergedAttributes": {
    "color": "red",
    "size": "M",
    "price": 29.99
  }
}

示例3:在 $group 阶段使用 $mergeObjects

在某些情况下,我们可能希望在聚合管道中的 $group 阶段合并对象。假设我们有一个集合,其中每个文档包含订单信息,我们想根据客户ID分组,并合并每个客户的所有订单信息:

{ "_id": 1, "customerId": 1001, "order": { "item": "Laptop", "price": 1000 } }
{ "_id": 2, "customerId": 1001, "order": { "item": "Mouse", "price": 50 } }
{ "_id": 3, "customerId": 1002, "order": { "item": "Keyboard", "price": 70 } }

我们可以这样做:

db.orders.aggregate([
  {
    $group: {
      _id: "$customerId",
      allOrders: { $push: "$order" }
    }
  },
  {
    $project: {
      _id: 0,
      customerId: "$_id",
      mergedOrders: {
        $reduce: {
          input: "$allOrders",
          initialValue: {},
          in: { $mergeObjects: ["$$value", "$$this"] }
        }
      }
    }
  }
])

输出结果将是:

{
  "customerId": 1001,
  "mergedOrders": {
    "item": "Mouse",
    "price": 50
  }
},
{
  "customerId": 1002,
  "mergedOrders": {
    "item": "Keyboard",
    "price": 70
  }
}

在这个例子中,注意到由于 $mergeObjects 会覆盖相同的字段,因此最后一个订单的 itemprice 会覆盖之前的值。如果需要合并的是数组或其他结构,可能需要不同的操作符和策略。

这些示例展示了如何在不同场景下使用 $mergeObjects。实际使用中可以根据具体需求进行调整。

  • $arrayToObject 是一个聚合操作符,用于将一个数组转换为一个对象。该操作符的输入数组中的每个元素可以是一个键值对数组或一个具有 kv 字段的对象。该操作符可以在聚合管道的多个阶段使用,特别是在 $project$addFields$group 等阶段。

示例1:从键值对数组转换为对象

假设我们有一个集合 students,每个文档包含学生的基本信息和他们的成绩,成绩存储在一个键值对数组中,如下所示:

{
  "_id": 1,
  "name": "Alice",
  "grades": [
    { "subject": "math", "score": 85 },
    { "subject": "english", "score": 90 }
  ]
}

我们想要将 grades 数组转换为一个对象,其中科目名称作为键,分数作为值。可以使用 $arrayToObject 操作符来完成这一任务:

db.students.aggregate([
  {
    $project: {
      _id: 0,
      name: 1,
      grades: {
        $arrayToObject: {
          $map: {
            input: "$grades",
            as: "grade",
            in: { k: "$$grade.subject", v: "$$grade.score" }
          }
        }
      }
    }
  }
])

输出结果将是:

{
  "name": "Alice",
  "grades": {
    "math": 85,
    "english": 90
  }
}

示例2:从键值对数组转换为对象(另一种格式)

假设我们有一个集合 products,每个文档包含产品的基本信息和属性,属性存储在一个键值对数组中,如下所示:

{
  "_id": 1,
  "name": "Laptop",
  "attributes": [
    ["color", "silver"],
    ["weight", "1.5kg"]
  ]
}

我们可以使用 $arrayToObject 直接将这个键值对数组转换为对象:

db.products.aggregate([
  {
    $project: {
      _id: 0,
      name: 1,
      attributes: { $arrayToObject: "$attributes" }
    }
  }
])

输出结果将是:

{
  "name": "Laptop",
  "attributes": {
    "color": "silver",
    "weight": "1.5kg"
  }
}

示例3:在 $group 阶段使用 $arrayToObject

假设我们有一个集合 sales,每个文档记录了不同商店的销售数据。我们想按商店分组,并将所有销售数据转换为一个对象,其中键是产品名称,值是销售数量:

{ "_id": 1, "store": "StoreA", "product": "Laptop", "quantity": 5 }
{ "_id": 2, "store": "StoreA", "product": "Mouse", "quantity": 10 }
{ "_id": 3, "store": "StoreB", "product": "Keyboard", "quantity": 7 }

我们可以这样做:

db.sales.aggregate([
  {
    $group: {
      _id: "$store",
      products: {
        $push: { k: "$product", v: "$quantity" }
      }
    }
  },
  {
    $project: {
      _id: 0,
      store: "$_id",
      salesData: { $arrayToObject: "$products" }
    }
  }
])

输出结果将是:

{
  "store": "StoreA",
  "salesData": {
    "Laptop": 5,
    "Mouse": 10
  }
},
{
  "store": "StoreB",
  "salesData": {
    "Keyboard": 7
  }
}

在这个例子中,我们首先使用 $group 阶段将每个商店的销售数据收集到一个数组中,然后使用 $arrayToObject 将这个数组转换为一个对象。

这些示例展示了如何在不同场景下使用 $arrayToObject。实际使用中可以根据具体需求进行调整。

  • MongoDB 的 $objectToArray 是一个聚合管道操作符,用于将对象转换为键值对的数组形式。具体来说,它将对象中的每个字段转换为一个包含 k(键) 和 v(值)的子文档。

使用场景

你可以使用 $objectToArray 当你需要将一个嵌套对象转换为数组来处理或操作字段时。

基本语法

{
  $objectToArray: <object>
}
  • <object>:要转换的对象。

示例

1. 将对象转换为键值对数组

假设你有以下文档:

{
  "_id": 1,
  "user": {
    "name": "Alice",
    "age": 30
  }
}

你想要将 user 对象转换为键值对数组,可以使用 $objectToArray。查询如下:

db.collection.aggregate([
  {
    $project: {
      userArray: { $objectToArray: "$user" }
    }
  }
])

结果:

{
  "_id": 1,
  "userArray": [
    { "k": "name", "v": "Alice" },
    { "k": "age", "v": 30 }
  ]
}

使用 $objectToArray 的限制:

  • 该操作符只能用于对象类型字段,且转换后的数组可以通过 MongoDB 的其他管道操作符进一步处理,例如 $filter$map 等。

$objectToArray 可以方便地将对象转换为数组,适合需要将对象解构为键值对以进一步操作的情况。

  1. 聚合管道

以下是 MongoDB 中所有的聚合表达式及其简要说明:

  1. $accumulator:定义用户自定义累加器。
  2. $addFields:添加新字段到文档。
  3. $bucket:将文档按照指定条件分桶。
  4. $bucketAuto:自动根据指定桶大小将文档分桶。
  5. $collStats:返回集合的统计信息。
  6. $count:计算文档数量。
  7. $currentOp:显示当前正在执行的操作。
  8. $facet:按多个不同的管道处理同一个输入文档集合。
  9. $geoNear:返回地理空间索引中距离给定点最近的文档。
  10. $graphLookup:使用递归查找关联文档。
  11. $group:按指定字段分组聚合。
  12. $indexStats:返回集合索引的统计信息。
  13. $limit:限制返回文档数量。
  14. $listLocalSessions:列出当前会话。
  15. $listSessions:列出所有会话。
  16. $lookup:在两个集合之间执行左外连接。
  17. $match:筛选文档。
  18. $merge:合并管道结果到集合。
  19. $out:将聚合结果输出到新的集合。
  20. $planCacheStats:返回查询计划缓存的统计信息。
  21. $project:投影出指定字段。
  22. $redact:根据权限控制规则将文档裁剪为子集。
  23. $replaceRoot:替换文档根。
  24. $replaceWith:替换文档为指定文档。
  25. $sample:随机获取样本文档。
  26. $set:设置字段值。
  27. $setWindowFields:为每个窗口文档设置新字段。
  28. $skip:跳过指定数量的文档。
  29. $sort:对文档进行排序。
  30. $sortByCount:按字段值计数并排序。
  31. $unionWith:将来自不同集合的文档合并到结果集。
  32. $unset:删除文档的字段。
  33. $unwind:展开数组字段。

这些聚合表达式是 MongoDB 中常用的,每个表达式都有不同的作用,可以根据实际需求进行选择和使用。详细的聚合表达式和用法可以参考 MongoDB 官方文档。