跳到主要内容

查询语法详解

本章深入介绍 MongoDB 的查询语法,包括查询运算符、正则表达式、地理空间查询等高级功能。

查询运算符详解

比较运算符

// $eq - 等于
db.users.find({ age: { $eq: 25 } })
// 简写形式
db.users.find({ age: 25 })

// $ne - 不等于
db.users.find({ status: { $ne: "deleted" } })

// $gt / $gte - 大于 / 大于等于
db.products.find({ price: { $gt: 100 } })
db.products.find({ price: { $gte: 100 } })

// $lt / $lte - 小于 / 小于等于
db.products.find({ price: { $lt: 1000 } })
db.products.find({ price: { $lte: 1000 } })

// $in / $nin - 在数组中 / 不在数组中
db.users.find({ role: { $in: ["admin", "moderator"] } })
db.products.find({ category: { $nin: ["outlet", "clearance"] } })

元素查询

// $exists - 检查字段是否存在
// 查找有 email 字段的用户
db.users.find({ email: { $exists: true } })

// 查找没有 phone 字段的用户
db.users.find({ phone: { $exists: false } })

// $type - 根据 BSON 类型查询
// 查找 age 字段为数字类型的文档
db.users.find({ age: { $type: "number" } })

// 常见类型:double, string, object, array, bool, null, int, long
db.users.find({ score: { $type: "number" } })

评估运算符

// $regex - 正则表达式匹配
db.users.find({ username: { $regex: "^zhang" } })
db.users.find({ email: { $regex: /@example\.com$/ } })

// $text - 文本搜索(需要创建文本索引)
db.articles.find({ $text: { $search: "mongodb tutorial" } })

// $where - JavaScript 表达式(性能较差,慎用)
db.users.find({ $where: "this.age > 25 && this.name.startsWith('z')" })

// $mod - 取模运算
db.products.find({ price: { $mod: [100, 0] } }) // price % 100 == 0

数组查询

// 准备测试数据
db.students.insertMany([
{ name: "Alice", scores: [85, 90, 92] },
{ name: "Bob", scores: [70, 75, 80] },
{ name: "Charlie", scores: [95, 88, 91] }
])

// $all - 数组包含所有指定元素
db.students.find({ scores: { $all: [85, 90] } })

// $elemMatch - 数组中至少有一个元素匹配条件
db.students.find({ scores: { $elemMatch: { $gt: 90 } } })

// $size - 数组长度
db.students.find({ tags: { $size: 3 } })

// 索引访问 - 获取数组特定位置的元素
db.students.find({ "scores.0": { $gt: 80 } }) // 第一个分数 > 80

嵌套文档查询

// 准备数据
db.orders.insertMany([
{
orderId: "ORD001",
customer: { name: "张三", city: "Beijing", age: 30 },
items: [{ product: "iPhone", price: 999 }, { product: "Case", price: 30 }]
},
{
orderId: "ORD002",
customer: { name: "李四", city: "Shanghai", age: 25 },
items: [{ product: "MacBook", price: 1999 }]
}
])

// 点符号查询嵌套字段
db.orders.find({ "customer.city": "Beijing" })

// 精确匹配嵌套文档(字段顺序必须一致)
db.orders.find({ customer: { name: "张三", city: "Beijing" } })

// 嵌套数组中的文档查询
db.orders.find({ "items.product": "iPhone" })

// 嵌套数组中特定条件的文档
db.orders.find({ "items": { $elemMatch: { price: { $gt: 500 } } } })

正则表达式查询

MongoDB 支持正则表达式进行模式匹配:

// 基本用法
db.users.find({ username: { $regex: "zhang" } })

// 使用正则表达式字面量
db.users.find({ email: { $regex: /@gmail\.com$/i } }) // i 表示不区分大小写

// 正则表达式选项
// i - 不区分大小写
// m - 多行模式
// x - 忽略空白字符
db.users.find({ bio: { $regex: "developer|engineer", $options: "i" } })

// 常用正则模式
// 以某字符串开头
db.users.find({ username: { $regex: "^admin" } })

// 以某字符串结尾
db.users.find({ email: { $regex: "@example\\.com$" } })

// 包含某字符串
db.users.find({ name: { $regex: "zhang" } })

// 精确匹配(忽略正则)
db.users.find({ name: "张三" }) // 比正则更高效

地理空间查询

MongoDB 支持地理位置查询,需要使用 GeoJSON 格式:

// 创建带地理位置的文档
db.places.insertMany([
{
name: "Central Park",
location: { type: "Point", coordinates: [-73.965355, 40.782865] },
category: "park"
},
{
name: "Times Square",
location: { type: "Point", coordinates: [-73.9855, 40.7580] },
category: "attraction"
},
{
name: "Empire State Building",
location: { type: "Point", coordinates: [-73.9857, 40.7484] },
category: "building"
}
])

// 创建 2dsphere 索引(用于地球表面计算)
db.places.createIndex({ location: "2dsphere" })

// $near - 查询附近的点(返回距离)
db.places.find({
location: {
$near: {
$geometry: { type: "Point", coordinates: [-73.97, 40.77] },
$maxDistance: 5000 // 5 公里
}
}
})

// $geoWithin - 查询范围内的几何图形
db.places.find({
location: {
$geoWithin: {
$centerSphere: [[-73.97, 40.77], 5 / 6378.1] // 5 公里
}
}
})

投影操作符

投影用于控制返回的字段:

// 1 - 包含字段,0 - 排除字段
db.users.find({}, { username: 1, email: 1 }) // 只返回 username 和 email(默认包含 _id)

// 排除 _id 字段
db.users.find({}, { _id: 0, username: 1, email: 1 })

// 排除敏感字段
db.users.find({}, { password: 0, salt: 0 })

// 条件投影 - $slice(返回数组的一部分)
db.orders.find({}, { items: { $slice: 5 } }) // 前 5 个元素
db.orders.find({}, { items: { $slice: [5, 10] } }) // 跳过 5 个,返回 10 个

// 条件投影 - $elemMatch(返回数组中匹配的第一个元素)
db.students.find(
{ scores: { $elemMatch: { $gt: 90 } } },
{ name: 1, "scores.$": 1 }
)

// $ - 数组元素投影(返回匹配的第一个元素)
db.students.find(
{ "scores.score": { $gt: 90 } },
{ name: 1, "scores.$": 1 }
)

查询修饰符

// sort - 排序
db.users.find().sort({ age: 1, name: -1 }) // 年龄升序,名字降序

// limit - 限制返回数量
db.products.find().limit(10)

// skip - 跳过指定数量(分页)
db.products.find().skip(20).limit(10)

// countDocuments - 获取文档数量
db.users.countDocuments({ age: { $gt: 25 } })

// estimatedDocumentCount - 快速估算数量(不使用索引)
db.users.estimatedDocumentCount()

// hint - 强制使用特定索引
db.users.find({ age: { $gt: 25 } }).hint({ age: 1 })

// explain - 查看查询计划
db.users.find({ age: { $gt: 25 } }).explain()

聚合管道查询

聚合管道是 MongoDB 强大的数据处理框架:

// 计算每个类别的平均价格和商品数量
db.products.aggregate([
{ $group: {
_id: "$category",
avgPrice: { $avg: "$price" },
count: { $sum: 1 }
}},
{ $sort: { count: -1 } }
])

// 多阶段管道
db.orders.aggregate([
// 匹配阶段
{ $match: { status: "completed" } },
// 解构数组
{ $unwind: "$items" },
// 分组统计
{ $group: {
_id: "$items.product",
totalQuantity: { $sum: "$items.quantity" },
totalRevenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } }
}},
// 排序
{ $sort: { totalRevenue: -1 } },
// 限制输出
{ $limit: 10 }
])

查询优化技巧

1. 避免全表扫描

// 不好的做法:正则表达式以 ^ 开头会导致全表扫描
db.users.find({ email: { $regex: "^.*@gmail.com" } })

// 好的做法:使用精确匹配或前缀索引
db.users.find({ email: "[email protected]" })
db.users.find({ email: { $regex: "^user@" } }) // 前缀匹配可以使用索引

2. 使用投影减少返回数据

// 只返回需要的字段
db.orders.find(
{ customerId: 123 },
{ orderId: 1, total: 1, status: 1 }
)

3. 合理使用索引

// 创建复合索引
db.users.createIndex({ age: 1, status: 1 })

// 使用索引进行排序
db.users.find({ age: { $gt: 20 } }).sort({ age: 1 })

4. 分页优化

// 低效的分页(skip 大数据时性能差)
db.orders.find().skip(10000).limit(10)

// 优化:使用游标或基于 ID 的分页
// 记录上一页最后一条的 _id
db.orders.find({ _id: { $gt: lastId } }).limit(10)

常用查询模式

// 1. 登录验证
db.users.findOne({
email: userEmail,
password: hashedPassword
})

// 2. 分页查询
db.articles.find({ published: true })
.sort({ createdAt: -1 })
.skip((page - 1) * pageSize)
.limit(pageSize)

// 3. 模糊搜索
db.products.find({
name: { $regex: keyword, $options: "i" }
})

// 4. 复杂条件查询
db.orders.find({
createdAt: {
$gte: new Date("2024-01-01"),
$lt: new Date("2024-02-01")
},
status: { $in: ["completed", "shipped"] },
"customer.country": "China",
total: { $gte: 100 }
})

小结

本章我们学习了:

  1. 比较运算符eq,eq, ne, gt,gt, gte, lt,lt, lte, in,in, nin
  2. 元素查询exists,exists, type
  3. 数组查询all,all, elemMatch, $size
  4. 嵌套文档查询:点符号访问
  5. 正则表达式:模式匹配
  6. 地理空间查询near,near, geoWithin
  7. 投影操作符:字段选择和数组切片
  8. 查询优化:避免全表扫描,合理使用索引