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 是"小明"的文档:

8. 查询操作符速查表

小结

这一篇把 MongoDB 的查询操作符系统过了一遍:比较、逻辑、元素、数组、正则,加上投影/排序/分页。从此 find 不再只会"等于",而能表达任意复杂的查询条件。下一篇趁热打铁讲更新操作符——$set$inc$push$pull,让你能精准修改文档的每一个角落。

← 上一篇 MongoDB 增删改查

下一篇 MongoDB 更新操作符

✈️💬