是什么
聚合表达式里的字符串函数把文本处理搬到服务端:$concat 拼接、$split 拆分、$toUpper/$toLower 改大小写、$substrCP 按码点截取、$regexMatch 做正则判断、$trim 系列去空白。它们按文档逐条计算,不像普通查询条件那样能用索引,所以正确做法是先用 $match 缩小范围,再在剩余的文档上做字符串处理。
基本信息
| 拼接与拆分 | $concat、$split |
|---|---|
| 大小写与空白 | $toUpper、$toLower、$trim、$ltrim、$rtrim |
| 截取与定位 | $substrCP、$substrBytes、$strLenCP、$indexOfCP |
| 正则 | $regexMatch、$regexFind、$regexFindAll(PCRE,可用 options) |
| 替换与转换 | $replaceOne、$replaceAll(4.4+)、$toString、$convert |
| 索引能力 | 管道内的字符串表达式不走索引,过滤尽量用 $match 的查询语法 |
典型使用场景
- 拼装展示字段:把姓与名、省市与区拼成一列,供报表或导出直接使用
- 拆分路径或标签:按斜杠、逗号切开字符串,取出层级或标签项
- 正则归类:用 $regexMatch 判断编号前缀、邮箱域名,生成分类字段
- 数据清洗:去掉首尾空白、统一大小写,便于后续按规则去重
- 字段标准化:用 $toString 把数值型编号转成字符串,统一不同来源的字段类型
字符串处理示例
字符串处理示例
db.products.aggregate([
{ $match: { sku: /^A-/ } }, # 先用正则前缀条件过滤,可命中索引
{ $addFields: {
shortName: { $substrCP: ["$name", 0, 4] }, # 按码点截取,中文不会被切坏
isA: { $regexMatch: { input: "$sku", regex: "^A-" } },
parts: { $split: ["$path", "/"] }, # 拆成数组,供后续 $size 统计
shown: { $concat: ["$brand", "-", { $toString: "$model" }] } # 非字符串要先转换
} },
{ $match: { isA: true } } # 对计算出的字段再做过滤
])
db.logs.aggregate([ # 清洗后统计
{ $addFields: { clean: { $trim: { input: "$raw", chars: " \\n\\t" } } } },
{ $group: { _id: "$clean", n: { $sum: 1 } } }
])常用函数读法
| 函数 | 作用 | 注意点 |
|---|---|---|
| $concat | 按顺序拼接字符串 | 任一参数为 null 或缺失时整体返回 null |
| $substrCP | 按码点截取子串 | 推荐用于中文,不会截断多字节字符 |
| $substrBytes | 按字节截取 | 在多字节字符中间截断会报错 |
| $regexMatch | 返回是否匹配的布尔值 | 不能使用索引,尽量放在 $match 之前缩小数据集 |
| $toString | 转成字符串 | 拼接前必须显式转换,否则报类型错误 |
处理中文一律用 $substrCP 而不是 $substrBytes:前者按码点计数,截取长度与肉眼看到的字符数一致;后者按字节,落在多字节字符中间会直接报错。
把正则可以前移的直接写进 $match 查询条件(例如 ^A- 这类前缀锚定),这部分能命中索引;一旦写进 $regexMatch 表达式就只剩逐文档计算,性能差别很大。