MongoDB 查询操作符
上一篇的 find 只会"字段等于某值"。真实的查询往往要表达"大于"、"在某几个值里"、"满足 A 或 B"、"字段是否存在"这类条件——这就需要 MongoDB 提供的一批以 $ 开头的查询操作符。掌握它们是 MongoDB 查询能力的关键。
1. 比较操作符
比较操作符放在字段值的位置,表示"该字段与某值的比较关系"。这是最高频的一组。
// 比较操作符:都是 $ 开头,放在字段值的位置
db.posts.find({ views: { $gt: 10 } }) // 大于 10
db.posts.find({ views: { $gte: 10 } }) // 大于等于
db.posts.find({ views: { $lt: 5 } }) // 小于
db.posts.find({ views: { $lte: 5 } }) // 小于等于
db.posts.find({ views: { $ne: 0 } }) // 不等于
db.posts.find({ views: { $eq: 5 } }) // 等于(其实等于直接写 5)
// 范围:10 到 50 之间(多个操作符写在一个对象里 = AND)
db.posts.find({ views: { $gte: 10, $lte: 50 } })
// 在指定集合里(任一匹配)
db.posts.find({ tags: { $in: ["入门", "笔记"] } })
// 不在指定集合里
db.posts.find({ status: { $nin: ["deleted", "draft"] } })
// 取模:views 除 5 余 0
db.posts.find({ views: { $mod: [5, 0] } })记忆口诀:$gt(greater than,大于)、$lt(less than,小于)、加 e 表示 equal(等于版)、$ne(not equal)、$in / $nin(在/不在集合里)。$gte 和 $lte 写在同一个对象里表示"区间"。
2. 逻辑操作符
MongoDB 默认多个字段条件是 AND 关系。要表达 OR 必须显式用 $or。
// 多个条件写在同一个对象里 = 隐式 AND
db.posts.find({ views: { $gt: 5 }, status: "published" })
// 显式 AND(条件较多或想复用对象时用)
db.posts.find({ $and: [
{ views: { $gt: 5 } },
{ status: "published" },
{ tags: "入门" }
]})
// OR:任一满足
db.posts.find({ $or: [
{ views: 0 },
{ views: { $gt: 100 } }
]})
// NOR:全部不满足(用得少)
db.posts.find({ $nor: [
{ status: "draft" },
{ status: "deleted" }
]})
// NOT:对条件取反
db.posts.find({ views: { $not: { $gt: 10 } } })
// 等价于 views <= 10 或字段不存在一个经典坑:逗号不是 OR
新手最常误解的一点——同一个查询对象里多个字段用逗号分隔是 AND;甚至同一个字段写两次也是后者覆盖前者,不是 OR!这点必须牢记:
// 一个经典坑:同一字段多个条件用逗号,不是 OR!
db.posts.find({ tags: "入门", tags: "笔记" })
// 这个查询其实只查 tags 等于 "笔记"(后者覆盖前者)
// 想表达 "tags 同时包含 入门 和 笔记" 用 $all
db.posts.find({ tags: { $all: ["入门", "笔记"] } })
// 想表达 "tags 等于 入门 或 等于 笔记" 用 $in
db.posts.find({ tags: { $in: ["入门", "笔记"] } })
// 同字段多条件需要 AND 时,用 $and 显式包起来
db.posts.find({ $and: [
{ views: { $gt: 5 } },
{ views: { $lt: 100 } }
]})
// 虽然这种情况直接 { views: { $gt: 5, $lt: 100 } } 更简洁3. 元素操作符
MongoDB schema 灵活意味着不同文档可能字段都不一样。这时候 $exists 和 $type 就派上用场了。
// $exists:字段是否存在(不管值)
db.posts.find({ cover: { $exists: true } }) // 有 cover 字段
db.posts.find({ cover: { $exists: false } }) // 没有 cover 字段
// $type:按 BSON 类型筛选
db.posts.find({ views: { $type: "int" } }) // 类型是 int
db.posts.find({ views: { $type: ["int", "double"] } }) // 多种类型
// 常用类型代号:
// "double"(1) "string"(2) "object"(3) "array"(4)
// "bool"(8) "int"(16) "long"(18) "decimal"(19)
// 出现多种类型通常说明数据不严谨,可以用 $type 排查$exists 在"渐进式加字段"场景特别有用:你上周给 posts 加了个 cover 字段,但老文章没有——查"哪些文章还没补封面",就是 find{ cover: { $exists: false } }。
4. 数组操作符
MongoDB 的文档可以包含数组字段,针对数组有专门的操作符。数组查询是面试和实战的高频考点。
// 数组字段查询:数组里包含某元素即可
db.posts.find({ tags: "入门" })
// 文档 tags 是 ["入门", "笔记"] -> 匹配
// 文档 tags 是 "入门"(标量) -> 也匹配
// $all:数组同时包含所有指定元素(顺序无关)
db.posts.find({ tags: { $all: ["入门", "笔记"] } })
// $size:数组长度等于
db.posts.find({ tags: { $size: 2 } }) // 数组长度恰好 2
// 注意 $size 不支持范围,要范围请存一个 length 字段
// $elemMatch:数组里至少一个元素满足所有条件
// 比如成绩数组 [{name:"数学",score:90},{name:"英语",score:60}]
db.scores.find({ grades: {
$elemMatch: { name: "数学", score: { $gte: 85 } }
}})
// 按位置精确匹配(下标从 0 开始)
db.posts.find({ "tags.0": "入门" }) // tags 第一个元素是 "入门"$elemMatch 是数组查询的灵魂——它保证"同一个数组元素"同时满足多个条件,而不是"不同元素各自满足一个"。比如查"数学成绩≥85 的学生",必须用 $elemMatch,否则 MongoDB 会理解成"数组里有个元素叫数学,另有个元素分数≥85"——张三的英语 60、数学 90,会被错判匹配。
5. 正则与字符串匹配
字符串模糊匹配用 $regex 或直接写 JS 正则字面量。MongoDB 的正则是 PCRE 风格,支持大部分常见语法。
// 正则:MongoDB 支持 PCRE 风格正则
db.posts.find({ title: /^Mongo/ }) // 以 Mongo 开头
db.posts.find({ title: /数据库/ }) // 包含 "数据库"
db.posts.find({ title: /mongo/i }) // 忽略大小写
// 也可以用 $regex 操作符(可加选项)
db.posts.find({ title: { $regex: "mongo", $options: "i" } })
// 注意:以前缀锚定的正则 /^xxx/ 能走索引(快)
// 不带 ^ 的全文匹配要全表扫描(慢),数据量大请用 $text 全文索引性能提示:只有左锚定(以 ^ 开头)的正则才能走索引,否则全表扫描。如果常用全文搜索,建议建 text 索引(后面索引篇会讲)。
6. 投影、排序、分页
这些不是"操作符"而是 find 的修饰,但和查询密不可分。投影(projection)控制返回哪些字段——大文档场景能显著减少网络传输。排序用 sort,分页用 skip + limit。
// 投影(projection):第二个参数控制返回哪些字段
// 1 = 包含, 0 = 排除
db.posts.find({}, { title: 1, views: 1 }) // 只取 title、views(_id 默认带)
db.posts.find({}, { title: 1, _id: 0 }) // 显式去掉 _id
db.posts.find({}, { content: 0 }) // 不要 content,其他都要
// 投影数组里的元素:只返回前 2 个
db.posts.find({}, { tags: { $slice: 2 } })
// 排序:1 升序,-1 降序
db.posts.find().sort({ views: -1 })
db.posts.find().sort({ createdAt: 1, views: -1 }) // 多字段排序
// 分页:sort + skip + limit
db.posts.find().sort({ _id: 1 }).skip(20).limit(10) // 第 3 页
// 统计数量(支持条件)
db.posts.countDocuments({ views: { $gt: 100 } })
db.posts.estimatedDocumentCount() // 集合总文档数(快,基于元数据)注意 countDocuments 会真正扫描计数(支持条件),estimatedDocumentCount 则是基于元数据估算(快,但不能加条件)。查"集合总文档数"用后者。
7. 嵌套文档查询
文档里嵌套对象时,用点表示法访问内层字段。比如文档有 author: { name, email },查 author.name 是"小明"的文档:
find{ "author.name": "小明" }— 必须加引号,因为点号在字段名里。- 嵌套数组配
$elemMatch:"作者里至少有一位是管理员"。 - 嵌套层数不宜过深(超过 2-3 层),否则查询和更新都麻烦,考虑拆出来。
8. 查询操作符速查表
- 比较:
$eq$ne$gt$gte$lt$lte$in$nin - 逻辑:
$and$or$nor$not - 元素:
$exists$type - 数组:
$all$size$elemMatch - 字符串:
$regex$text - 地理:
$near$geoWithin$geoIntersects
小结
这一篇把 MongoDB 的查询操作符系统过了一遍:比较、逻辑、元素、数组、正则,加上投影/排序/分页。从此 find 不再只会"等于",而能表达任意复杂的查询条件。下一篇趁热打铁讲更新操作符——$set、$inc、$push、$pull,让你能精准修改文档的每一个角落。
← 上一篇 MongoDB 增删改查
下一篇 MongoDB 更新操作符 →