超越基础:用于数据分析的高级MongoDB查询命令

使用高级MongoDB过滤器、投影和聚合阶段,无需将数据移出数据库即可进行分析。

超越基础:用于数据分析的高级MongoDB查询命令

当你超越简单的find()调用时,MongoDB查询会变得更加有用。如果你需要过滤嵌套文档、重塑输出或计算分组结果,高级查询运算符和聚合阶段可以让你在数据附近完成这些工作。

以下示例侧重于你可以在mongosh中运行的实际命令,用于报告、故障排除和一次性分析。

使用查询运算符掌握复杂过滤

虽然基本的find()方法处理简单的相等检查,但高级分析通常需要组合多个条件或查询特定的字段结构。MongoDB提供了一组丰富的查询运算符来构建精细的过滤器。

用于复合查询的逻辑运算符

逻辑运算符允许你组合多个查询条件,从而对返回哪些文档进行精细控制。这些对于构建复杂的分析问题至关重要。

  • $and / 隐式$and:用于指定必须全部满足的多个条件。虽然通常是隐式的(在查询对象中顺序列出条件),但当多次查询同一字段时,$and是必需的。
    // 隐式$and:查找年龄大于25岁且居住在纽约的用户
    db.users.find({ age: { $gt: 25 }, city: "New York" });
    
    // 显式$and:查找'score'大于90或'level'为5的文档
    db.results.find({ $and: [ { score: { $gt: 90 } }, { level: 5 } ] });
    
  • $or:选择与指定表达式数组中任意一个匹配的文档。
    db.products.find({ $or: [ { category: "Electronics" }, { price: { $lt: 100 } } ] });
    
  • $not:否定指定表达式的结果。

地理空间和数组运算符

对于基于位置的数据或复杂数组,专门的运算符提供了分析能力:

  • $geoWithin / $near:对于查找特定地理区域或邻近区域内的数据至关重要。
  • $elemMatch:对于查询嵌入文档的数组至关重要,确保数组中的一个元素匹配$elemMatch内的所有指定条件。
    // 查找'items'数组中至少有一个项目的价格超过500且数量大于1的订单
    db.orders.find({ items: { $elemMatch: { price: { $gt: 500 }, qty: { $gt: 1 } } } });
    

高级投影:塑造输出

投影,通过find()方法中的第二个参数管理,决定返回哪些字段。高级投影超越了简单的包含/排除,可以转换或塑造返回的数据。

字段排除和包含

  • 1 包含一个字段;0 排除一个字段。
  • 重要说明:你不能混合包含(1)和排除(0),除非是_id字段(默认包含,可以通过将其设置为0来显式排除)。
// 仅包含'name'和'email',排除'_id'
db.users.find({}, { name: 1, email: 1, _id: 0 });

数组切片和操作

投影可以使用$slice限制返回的数组元素数量:

  • $slice: N:返回前N个元素。
  • $slice: -N:返回最后N个元素。
  • $slice: [M, N]:从索引M开始返回N个元素。
// 仅返回'history'数组中的最后3个条目
db.logs.find({}, { history: { $slice: -3 } });

使用聚合框架分析数据

MongoDB聚合框架是进行复杂数据分析最强大的工具,它允许你通过管道中的多个阶段处理数据记录。每个阶段对从前一个阶段传递的数据执行特定的转换或操作。

关键聚合阶段

基本结构使用db.collection.aggregate([...pipeline])

1. $match(过滤)

功能类似于find(),但它在后续阶段之前应用,通过尽早减少数据集来优化性能。

2. $group(分组和计算)

此阶段按指定的标识符(_id)对文档进行分组,并应用累加器运算符来计算汇总统计信息。

常见累加器:

  • $sum
  • $avg
  • $min$max
  • $push(从组中收集数组数据)
// 计算每个部门的平均分数
db.scores.aggregate([
  { $group: { 
    _id: "$department", 
    averageScore: { $avg: "$score" },
    totalStudents: { $sum: 1 }
  } }
]);

3. $project(重塑文档)

在聚合中用于重塑输出文档,类似于find()投影,但通常用于创建新的计算字段。

  • 计算字段:你可以在投影阶段使用现有字段执行计算。
// 在管道内计算利润率
db.sales.aggregate([
  { $project: { 
    _id: 0, 
    productName: 1,
    profit: { $subtract: ["$salePrice", "$cost"] }
  } }
]);

4. $lookup(连接数据)

$lookup阶段从另一个集合中添加匹配的文档,类似于左外连接。当你需要为报告丰富文档而无需在应用程序代码中进行连接时,这很有用。

// 将'orders'集合与'customers'集合连接
db.orders.aggregate([
  { $match: { status: "Pending" } },
  { $lookup: {
      from: "customers",         // 要连接的集合
      localField: "customerId",  // 输入文档(orders)中的字段
      foreignField: "_id",       // "from"集合(customers)文档中的字段
      as: "customerDetails"      // 输出数组字段名称
  } }
]);

5. $unwind(解构数组)

如果数组字段包含多个元素,$unwind会为数组中的每个元素创建一个单独的输出文档,从而有效地对数据进行非规范化,以便更容易地对数组内容进行分组或过滤。

警告$unwind会显著增加文档数量。请谨慎使用,通常是在$match之后,以减少初始集合。

分析查询的最佳实践

  1. 索引入口点:为早期$match阶段和索引支持的$sort阶段使用的字段建立索引。$group可以从较少的输入文档中受益,但仅仅因为分组的字段有索引,它并不会自动变快。
  2. 尽早过滤:在聚合管道中尽可能早地放置$match阶段。尽早减少文档数量可以为后续更昂贵的阶段(如$lookup$group)节省大量处理能力。
  3. 使用适当的数据类型:确保比较字段(如日期或数值)存储一致。类型不匹配会导致$match运算符静默或低效地失败。

当简单的数据检索不够时,请使用这些命令。从选择性过滤器开始,仅投影你需要的字段,并在需要分组、重塑或集合间丰富时进入聚合。