MongoDB 命令生成
用表单把查询条件一行行填出来,生成能直接粘进 mongosh 的可读语句,并附一份驱动能用的扩展 JSON。
可带投影、排序、limit 和 skip
$in 和 $nin 的值在同一格里用逗号分隔,工具会展开成数组;$regex 请选「正则」类型并填模式,标志位填 i、m 这类字符。字段名带点号表示嵌套字段,比如 profile.age。
投影里要么全用「包含」(只有 _id 例外可以混用),要么全用「排除」,混着写会被 MongoDB 拒绝。聚合管道里这几个只能配合对应的阶段使用:排序对应 $sort,投影对应 $project,limit 对应 $limit。
操作符速查
| 操作符 | 含义 |
|---|---|
| $eq | 等于。字段只有一个等于条件时可以省略,直接写裸值 |
| $ne | 不等于。注意它也会命中「字段不存在」的文档 |
| $gt / $gte | 大于 / 大于等于。只能用于可比较的 BSON 类型 |
| $lt / $lte | 小于 / 小于等于。和 $gt 同时写要合并到同一个字段对象里 |
| $in | 值等于数组里的任意一个。数组写成一串逗号分隔的值,工具会展开成数组 |
| $regex | 正则匹配。shell 里写成 /模式/标志,等价 JSON 里写成 $regularExpression |
| $exists | 字段是否存在。值填 true 表示必须存在,false 表示必须不存在 |
| $set | 设置字段的值,不存在就新建 |
| $unset | 删除字段。工具会写成空字符串,Mongo 官方也推荐这种写法 |
| $inc / $mul | 在原值上累加 / 相乘,字段不存在时按 0 或 1 起算 |
| $push / $addToSet | 往数组里追加;$addToSet 会自动去重 |
使用说明
MongoDB 的查询过滤器和驱动用的 JSON 长得几乎一样,但有两个类型在 JSON 里根本不存在:ObjectId 和日期。集合里的 _id 默认是 ObjectId,如果查询里写成字符串形式,两者类型不同,一条都匹配不到,而且不会有任何报错提示。日期同理。所以生成的可读语句里会写 ObjectId("...")、ISODate("...") 和正则字面量,扩展 JSON 则用 $oid、$date、$regularExpression 表达同样的东西,驱动和 Compass 都认识。
需要特别小心的是字段重复。JavaScript 对象里同名键后面的会覆盖前面的,所以 { age: { $gte: 18 }, age: { $lt: 65 } } 实际只剩后一个条件,看起来两个条件都在,结果却少了一半。正确写法是把条件合并进同一个字段对象:{ age: { $gte: 18, $lt: 65 } }。这一页在同一个字段出现多条时会自动做这个合并;只有一条 $eq 时仍写成 { status: "active" } 这样的裸值,读起来更像手写的查询。
更新操作有个容易出事故的地方:MongoDB 只看更新文档里有没有更新运算符。带 $set、$inc 这些运算符是「局部修改」,不带则被当作「整份替换」,会把这条文档除 _id 以外的字段全部删掉再写入新内容。所以更新区只提供运算符,不给你机会生成替换式更新。另外 $unset 只需要字段名,工具会把它写成空字符串——这是官方文档推荐的写法,写 1 也可以,但和字段名更容易混淆。
聚合的写法是把若干个阶段串成一个数组,每个阶段是一个单键对象。这一页按 $match、$group、$sort、$project、$limit 的顺序拼接,用按钮决定每个阶段出现与否。顺序是固定的,因为顺序真的会影响结果:$match 越靠前越能用上索引,先把文档数量降下来,后面的 $group 和 $sort 才不会扛着全量数据做计算;反过来说,$limit 放在 $sort 之前和之后完全是两回事。$group 里的 _id 用带美元符号的字符串表示字段路径,留空则写成 _id: null,表示把所有文档当成一组来统计。
最后是一句安全提醒:更新的删除类操作,条件为空就意味着命中整个集合,deleteMany 会把数据清空。生成语句之前请先用同样的条件跑一遍 find 看看命中哪些文档,或者用 explain 确认走没走索引;$in 的值在同一格里用逗号分隔,值本身含逗号时用引号包起来。这页只负责把语句拼对,不会连接任何数据库,也不会上传你的条件。想在 JSON 上做校验和格式化用JSON 格式化,测试 $regex 里的模式用正则测试,写 SQL 那边的查询可以先用SQL 格式化整理。
常见问题
为什么不能直接把表单值拼成 JSON 就丢给 Mongo?
因为 JSON 里没有 ObjectId 和日期这两个类型。把 _id 写成字符串,查询会一条都匹配不上——集合里存的是 ObjectId,类型不同不相等;把日期写成字符串也一样。shell 里必须写成 ObjectId("...") 和 ISODate("..."),正则则写成 /模式/标志。所以这里把可读的 shell 语句和给驱动用的扩展 JSON 分成两份输出。
同一个字段加了两个条件,生成的语句为什么合并成了一个对象?
因为 JavaScript 对象不允许有两个同名键,写 { age: 18, age: 30 } 后面的会直接覆盖前面,而且不会报错,是最隐蔽的一类错。正确的写法是两个条件合并成 { age: { $gte: 18, $lt: 65 } },所以工具在检测到同一字段出现多次时会自动合并进一个运算符对象里。只有一条 $eq 时仍然保留 { status: "active" } 这种裸值写法,和手写习惯一致。
$exists 的值该填什么?
它要的是布尔值,不是字段名。填 true 表示「这个字段必须存在」,填 false 表示「这个字段必须不存在」——注意不是「字段存在但值为 false」。工具把 true 之外的写法一律当成 false,所以别把值留空,否则会得到「必须不存在」这个大概率不是你想要的语义。
updateOne 的第二个参数为什么必须带 $set 之类的运算符?
MongoDB 对更新的语义只有两种:传带更新运算符的文档是「局部修改」,传普通文档则是「整份替换」。后者很容易出事故——{ name: "张三" } 会把这条文档除 _id 之外的所有字段删掉只剩 name。所以这一页的更新区只让填 $set、$unset、$inc 这类运算符,避免生成出替换式更新。
聚合管道的阶段顺序可以自己调吗?
这一页按 $match、$group、$sort、$project、$limit 的固定顺序串联,每个阶段用按钮决定要不要出现,不能拖拽排序。原因是顺序确实会影响结果,但也确实有一套通用的惯例:$match 尽量放在最前面,先用索引把数据缩小,后面的 $group 和 $sort 才不用处理全量文档。真要自定义顺序,把生成的管道复制出去再手改比在表单里拖更清楚。
$group 里的 _id 写成 "$city" 是什么意思?
带美元符号的字符串在聚合表达式里表示「字段路径」,所以 "$city" 是取文档的 city 字段,而不是字面的 city 这个词。想按字段里嵌的路径分组就写 $profile.city。想统计所有文档而不分组,把分组键留空,工具会写成 _id: null。累加器里的 $count 其实是 $sum: 1 的简写,MongoDB 5.0 之后才有专门的 $count 阶段。
扩展 JSON 里的 $oid、$date、$regularExpression 是给谁看的?
给驱动和工具看的。JSON 标准没有 ObjectId 和日期类型,MongoDB 的扩展 JSON 用 $oid、$date、$regularExpression 这几个包装来表达它们;Node、Python、Java 的驱动,以及 Compass、mongosh 都能解析这种写法。把它直接粘进 Compass 的查询栏效果最好,粘进代码里则要先过一遍驱动提供的扩展 JSON 解析函数。