MongoDB 简介

条件查询与常用操作符

掌握比较与逻辑操作符、字段投影、排序分页,从集合中精确找出想要的数据。

🎯 引言

上一篇我们已经把四条用户数据(张三 25 北京、李四 30 上海、王五 22 广州、赵六 28 深圳)存进了 blog_demo 数据库的 user 集合。但 find() 不带条件会把所有数据都捞出来,实际开发中我们几乎总是带着条件去查。

学完这篇文章,你能用比较操作符逻辑操作符写出各种查询条件,用字段投影只取需要的字段,再配排序与分页控制返回结果,并能统计文档数量。这是日常查询 MongoDB 的核心技能,后面的课程都会反复用到。


🧱 比较操作符:大于、小于、不等于

先用统一命令连上 mongosh(容器和账号沿用第 01 篇的约定):

docker exec -it mongo-demo mongosh -u root -p 123456 --authenticationDatabase admin

进入后切换到 blog_demo 数据库:

use blog_demo;

MySQL 里的条件查询写在 WHERE 后面,MongoDB 则写在 find() 的第一个参数里,而且条件本身是一个 JavaScript 对象。比如「精确匹配」就是把字段和值直接写进对象:

db.user.find({ city: '北京' });

那「大于 25 岁」怎么写?字符串、数字可以直接放,但「大于」不是一个值,这时候就要用到操作符了。MongoDB 的操作符都以 $ 开头,写法是把字段的值换成一个对象,操作符作为这个对象的键:

// 查询年龄大于 25 的用户
db.user.find({ age: { $gt: 25 } });

可以打个比喻:直接写 age: 25 像是跟店员说「我要 25 号货架」;写 age: { $gt: 25 } 像是递上一张小纸条,纸条上写着「25 号往后的货架都要」。外层对象告诉 MongoDB 查哪个字段,内层对象告诉它用什么规则匹配。

常用的比较操作符有这几个:

操作符含义
$gt大于(>)
$gte大于等于(>=)
$lt小于(<)
$lte小于等于(<=)
$ne不等于(!=)
$in在指定数组之内

两个常用的操作符可以组合在同一个字段上,比如「年龄在 22 到 28 之间」:

db.user.find({ age: { $gte: 22, $lte: 28 } });

$in 表示「字段值在数组里的任意一个就算匹配」,比如查询北京或上海的用户:

db.user.find({ city: { $in: ['北京', '上海'] } });

$ne 则反过来,比如查询「不在北京的用户」:

db.user.find({ city: { $ne: '北京' } });

✨ 逻辑操作符:$and 与 $or

一个字段的条件可以用内层对象组合,但「多个字段之间的与、或」就要用到逻辑操作符了。

$and 表示所有条件同时成立。它接收一个条件数组,比如查询「年龄大于 25 并且城市是深圳」:

db.user.find({
    $and: [{ age: { $gt: 25 } }, { city: '深圳' }],
});
其实 $and 大部分时候可以省略:同一个条件对象里写多个字段,默认就是「并且」的关系。上面的查询写成 { age: { $gt: 25 }, city: '深圳' } 效果完全一样,日常更推荐这种简洁写法。$and 主要用于需要对同一字段写多个并列条件的少数场景。

$or 表示任意一个条件成立即可。比如查询「年龄小于 23,或者在上海的用户」:

db.user.find({
    $or: [{ age: { $lt: 23 } }, { city: '上海' }],
});

注意对比一下:$in 是「同一个字段,匹配多个值」,$or 是「多个条件(可以是不同字段),满足一个即可」。查「北京或上海」用 $in 更简洁;条件横跨不同字段时用 $or


💡 字段投影:只返回需要的字段

find() 默认把文档的所有字段都返回。但接口往往只需要其中两三个字段,全都传回去既浪费网络流量,也可能泄露敏感信息(比如密码字段)。

find()第二个参数专门用来控制返回哪些字段,这叫字段投影(Projection):

// 只返回 username 和 age
db.user.find({ age: { $gt: 25 } }, { username: 1, age: 1 });

投影对象里写 1 表示「我要这个字段」。执行后你会发现结果里多了一个 _id:它是 MongoDB 自动给每条文档生成的主键,默认总是返回。如果不要它,需要显式写 _id: 0 把它关掉:

db.user.find({ age: { $gt: 25 } }, { username: 1, age: 1, _id: 0 });
投影有个容易踩的坑:除了 _id1(要)和 0(不要)不能混用。写成 { username: 1, age: 0 } 会报错。要么统一写「我要哪些」,要么统一写「我不要哪些」,两种风格二选一,_id 是例外,可以随时单独关闭。

可以打个比喻:投影就像去打印店复印证件,勾选「只要姓名和年龄」两项,店员就只印这两栏,其余内容不复印。


⚡ 排序与分页:sort、limit、skip

查出来的数据往往还要排个序。sort() 接收一个对象,1 表示升序(从小到大),-1 表示降序(从大到小):

// 按年龄从大到小排序
db.user.find().sort({ age: -1 });

sort() 跟在 find() 后面链式调用即可,也可以指定多个排序字段,先按前一个排、相同再按后一个排。

分页则靠 limit()skip() 这对组合:limit(n) 限制最多返回 n 条,skip(n) 跳过前 n 条。假设每页显示 2 条,取第 2 页就是「跳过前 2 条,再取 2 条」:

// 每页 2 条,查询第 2 页(页码从 1 开始算)
db.user.find().sort({ age: -1 }).skip(2).limit(2);

配合排序,这条查询会按年龄降序取第 2 页,也就是年龄较小的王五和赵六前面被跳过的两人之后的两条。写分页时建议总是带上 sort(),否则返回顺序不固定,翻页可能出现重复或漏数据。

skip() 的原理是「先扫描再丢弃」,翻页越深要跳过的数据越多,速度就越慢。数据量变大后,常用做法是改用条件范围翻页:记住上一页末尾的值(比如年龄),下一页用 { age: { $lt: 上一页末尾年龄 } } 接着查。知道有这回事即可,后续用到再深入。

🛠 统计数量:countDocuments

有时我们不需要数据本身,只想知道「有多少条」,比如列表页顶部的「共 N 位用户」。这时用 countDocuments(),参数和 find() 的条件写法一样:

// 统计北京的用户数量
db.user.countDocuments({ city: '北京' });

返回 1。条件传空对象 {} 就是统计集合里的全部文档:

db.user.countDocuments({});

🧾 小节总结

  • 比较操作符以 $ 开头,写成字段的内层对象:$gt / $gte / $lt / $lte / $ne / $in
  • 同字段多条件写在一个对象里,如 { $gte: 22, $lte: 28 };多字段默认是「并且」,跨字段「或者」用 $or
  • find() 第二个参数做字段投影,写 1 表示返回该字段;_id 默认返回,显式设 0 才隐藏;10 不能混用(_id 除外)。
  • sort({ age: -1 }) 降序、1 升序;skip() + limit() 实现分页,翻页深时会变慢。
  • countDocuments(条件) 统计匹配的文档数量,传 {} 统计全部。

❓ 知识问答

Q1:{ city: '北京' }{ city: { $in: ['北京'] } } 有什么区别?

对单个值来说两者效果一样,直接用等值写法 { city: '北京' } 更简洁。$in 的价值在于一次匹配多个值,比如 { city: { $in: ['北京', '上海'] } }

Q2:$gt: 25 会包含 25 岁的人吗?

不会。$gt 是严格大于,要包含 25 请用 $gte。同理 $lt 是严格小于,包含边界用 $lte

Q3:为什么我的投影里写了 username: 1,结果还是有 _id

_id 是主键,默认总是返回,不受投影里 1 列表的影响。不想要它就显式写 _id: 0

Q4:skip(0).limit(2) 和直接 limit(2) 一样吗?

一样,跳过 0 条等于不跳过。skip() 只在翻到第 2 页及以后才真正发挥作用,第 1 页可以省略不写。

Q5:分页查询不带 sort() 会有什么问题?

不排序时文档的返回顺序不固定,翻页过程中可能出现同一条数据在多个页面重复出现,或者某些数据始终翻不到。分页前建议先确定排序字段。


🧪 小练习

user 集合中完成下面两步查询:

  1. 查询年龄大于 23 且不在北京的用户,只返回 usernamecity 两个字段(不要 _id)。
// 请在这里编写代码
  1. 按年龄从小到大排序,每页 2 条,查询第 2 页。
// 请在这里编写代码

🎉 恭喜你已经掌握条件查询与常用操作符的技能啦!下一篇我们聊聊文档与数据类型,看看 MongoDB 里的嵌套文档、数组这些灵活结构该怎么玩。