当前位置: 首页 > news >正文

【MongoDB】查询条件运算符:$expr 和 $regex 详解,以及为什么$where和$expr难以使用索引

一、$expr 运算符

$expr 允许你在查询语句中使用聚合表达式,实现字段间的比较和复杂条件查询。

注解$expr 主要用于比较同一文档中不同字段的值,或者使用聚合表达式进行条件判断。

基本语法

db.collection.find({$expr: {<聚合表达式>}
})

常见使用场景

1. 比较同一文档中的两个字段
// 查找库存量小于销售量的产品
db.products.find({$expr: { $lt: ["$stock", "$sales"] }
})
2. 使用条件逻辑
// 查找折扣价大于原价50%的产品
db.products.find({$expr: { $gt: ["$discountPrice", { $multiply: ["$price", 0.5] }] }
})

$expr 常用操作符

操作符描述示例
$eq等于$eq: ["$field1", "$field2"]
$ne不等于$ne: ["$field1", "value"]
$gt大于$gt: ["$field1", 100]
$lt小于$lt: ["$field1", "$field2"]
$and逻辑与$and: [expr1, expr2]
$or逻辑或$or: [expr1, expr2]
$concat字符串连接$concat: ["$fname", " ", "$lname"]
$substr字符串截取$substr: ["$name", 0, 3]

注意:使用 $expr 时,字段名前需要加 $ 符号,表示引用字段值而非字面量。

二、$regex 运算符

$regex 提供正则表达式功能,用于模式匹配字符串查询。

注解:正则表达式是强大的文本匹配工具,$regex 让你能在 MongoDB 中利用这一功能。

基本语法

db.collection.find({field: { $regex: /pattern/, $options: 'options' }
})// 或者
db.collection.find({field: { $regex: 'pattern', $options: 'options' }
})

常用选项($options)

选项描述
i不区分大小写
m多行匹配
x忽略空白字符
s允许点字符(.)匹配所有字符

使用示例

1. 简单匹配
// 查找名字以"Jo"开头的用户(不区分大小写)
db.users.find({name: { $regex: /^Jo/i }
})
2. 包含特定模式
// 查找邮箱包含"example.com"或"sample.com"的用户
db.users.find({email: { $regex: /(example|sample)\.com/ }
})
3. 使用字符串而非正则字面量
// 查找描述中包含"mongodb"的产品(不区分大小写)
db.products.find({description: { $regex: "mongodb", $options: 'i' }
})

性能考虑

重要提示:正则表达式查询通常无法使用索引,尤其是:

  • 当使用通配符开头(如 /^abc/ 可以使用索引,但 /abc$//.*abc/ 则不行)
  • 使用复杂正则表达式时

三、$expr 和 $regex 结合使用

你可以组合这两个运算符实现更复杂的查询:

// 查找全名(firstname + lastname)包含"Smith"且邮箱与用户名相同的用户
db.users.find({$expr: {$and: [{ $regexMatch: { input: { $concat: ["$firstname", " ", "$lastname"] }, regex: "Smith" } },{ $eq: ["$email", "$username"] }]}
})

注意:MongoDB 4.2+ 引入了 $regexMatch 等聚合运算符,使正则表达式在 $expr 中使用更方便。

四、总结对比

特性$expr$regex
主要用途字段间比较和复杂逻辑文本模式匹配
语法使用聚合表达式使用正则表达式
性能可以使用索引(取决于表达式)有限索引支持
版本需要 MongoDB 3.6+所有版本支持
典型场景比较同一文档中的多个字段搜索、模糊匹配

五、最佳实践建议

  1. 谨慎使用 $expr

    • 对于简单查询,优先使用常规查询操作符
    • 只在需要字段间比较或复杂逻辑时使用 $expr
  2. 优化 $regex 查询

    • 避免前导通配符(如 ^ 开头可以使用索引)
    • 考虑使用文本索引替代复杂正则表达式
    • 对常用模式考虑预计算字段
  3. 测试查询性能

    • 使用 explain() 方法分析查询执行计划
    • 对大集合进行性能测试

通过案例学习$expr用法

一、金融领域:风险交易检测

场景:检测异常大额交易(单笔超过账户日均余额30%的交易)

db.transactions.find({$expr: {$and: [// 确保amount和dailyBalance字段存在{ $ifNull: ["$amount", false] },{ $ifNull: ["$dailyBalance", false] },// 主条件:交易金额 > 日均余额的30%{ $gt: ["$amount",{ $multiply: ["$dailyBalance", 0.3] } // 计算日均余额的30%] },// 附加条件:交易时间在非工作时间(晚上8点到早上6点){$or: [{ $lt: [{ $hour: "$transactionTime" }, 6] },{ $gt: [{ $hour: "$transactionTime" }, 20] }]}]}
}).sort({ amount: -1 }).limit(100)

业务注释

  1. $ifNull 确保字段存在,避免空值报错
  2. $multiply 实现金额百分比计算
  3. $hour + $or 组合实现时间段过滤
  4. 最后排序并限制结果数量,便于风险团队优先处理大额交易

二、电商领域:动态定价监控

场景:找出定价低于成本价或异常折扣的商品(当前价<成本价 或 折扣>70%)

db.products.find({$expr: {$or: [// 情况1:当前售价低于成本价{ $lt: ["$currentPrice", "$costPrice"] },// 情况2:折扣力度超过70%(需先计算折扣率){$gt: [{ $divide: [{ $subtract: ["$originalPrice", "$currentPrice"] },"$originalPrice"]},0.7]}]},status: "active" // 只查询上架商品
})

业务注释

  1. $subtract 计算原价与现价的差额
  2. $divide 计算折扣百分比
  3. 组合使用 $or 覆盖两种异常情况
  4. 额外添加常规查询条件(status)与 $expr 配合使用

三、物流领域:时效性分析

场景:查找实际配送时间超过承诺时间2倍以上的订单

db.orders.aggregate([{$match: {$expr: {$and: [// 确保必要字段存在且已完成配送{ $gt: ["$actualDeliveryDate", null] },{ $gt: ["$promisedDeliveryDate", null] },{ $eq: ["$status", "delivered"] },// 计算时间差(毫秒){$gt: [{ $subtract: ["$actualDeliveryDate", "$orderDate"] },{ $multiply: [{ $subtract: ["$promisedDeliveryDate", "$orderDate"] },2]}]}]}}},{$project: {// 计算超时天数(展示用)delayDays: {$divide: [{$subtract: [{ $subtract: ["$actualDeliveryDate", "$orderDate"] },{ $subtract: ["$promisedDeliveryDate", "$orderDate"] }]},1000 * 60 * 60 * 24 // 毫秒转天数]},orderId: 1,customerId: 1}}
])

业务注释

  1. 使用聚合管道结合 $match$expr 实现复杂过滤
  2. 日期字段比较需转换为毫秒数计算
  3. $project 阶段将毫秒差转换为易读的天数
  4. 多层嵌套的表达式展示 MongoDB 强大的计算能力

四、人力资源:薪资合规审计

场景:检测薪资异常(当前薪资<入职薪资 或 涨幅超过职级上限)

db.employees.find({$expr: {$or: [// 异常情况1:当前薪资低于入职薪资{ $lt: ["$currentSalary", "$startingSalary"] },// 异常情况2:薪资涨幅超过职级允许上限{$let: {vars: {// 计算实际涨幅百分比actualIncrease: {$divide: [{ $subtract: ["$currentSalary", "$startingSalary"] },"$startingSalary"]},// 获取该职级的允许最大涨幅maxAllowed: {$switch: {branches: [{ case: { $eq: ["$grade", "P1"] }, then: 0.3 },{ case: { $eq: ["$grade", "P2"] }, then: 0.4 },{ case: { $eq: ["$grade", "P3"] }, then: 0.5 }],default: 0.2}}},in: {$gt: ["$$actualIncrease", "$$maxAllowed"]}}}]},department: { $in: ["Engineering", "Product"] } // 只检查特定部门
})

业务注释

  1. 使用 $let 定义临时变量简化复杂表达式
  2. $switch 实现职级与薪资上限的映射
  3. 多层嵌套的算术运算演示 MongoDB 的表达能力
  4. 最终与常规查询条件组合使用

五、物联网(IoT):设备异常监测

场景:找出传感器读数异常的设备(当前值超过3个标准差)

db.deviceReadings.find({$expr: {$gt: ["$currentValue",{$add: ["$avgValue",{ $multiply: ["$stdDev", 3] } // 计算3个标准差范围]}]},$where: "new Date() - this.lastMaintenanceDate > 1000*60*60*24*30" // 超过30天未维护
})

业务注释

  1. 统计学方法应用于设备监测(平均值+3标准差)
  2. $add$multiply 组合计算阈值
  3. 结合 $where 实现更复杂的脚本判断(注意性能影响)
  4. 适合大规模IoT设备的异常检测场景

六、进阶技巧:性能优化方案

优化建议1:为 $expr 常用字段组合创建复合索引

// 为物流案例创建优化索引
db.orders.createIndex({status: 1,actualDeliveryDate: 1,promisedDeliveryDate: 1
})// 为金融交易案例创建优化索引
db.transactions.createIndex({transactionTime: 1,amount: -1
})

优化建议2:使用 $redact 替代复杂 $expr

// 人力资源案例的替代方案
db.employees.aggregate([{$redact: {$cond: {if: {$or: [{ $lt: ["$currentSalary", "$startingSalary"] },{ $gt: ["$salaryIncreaseRatio", "$grade.maxAllowedRatio"] }]},then: "$$KEEP",else: "$$PRUNE"}}}
])

一、为什么$where$expr难以使用索引?

1. $where:使用JavaScript表达式查询

$where允许通过JavaScript代码定义查询条件(例如判断字段间的关系),示例:

// 查询"价格高于成本2倍"的商品
db.products.find({ $where: "this.price > this.cost * 2" })

无法有效使用索引的原因

  • 执行机制$where会对集合中的每个文档执行JavaScript代码,逐行判断条件是否成立。这种逐文档扫描的方式本质上是“全表扫描”,索引无法直接加速这个过程。
  • 索引失效场景:即使字段pricecost有索引,$where也无法利用它们,因为索引是基于字段值的有序结构,而$where的逻辑是动态计算的(依赖两个字段的运算结果)。
2. $expr:使用聚合表达式查询

$expr允许在查询中使用聚合管道的表达式(如$gt$add等),支持字段间的比较,示例:

// 与上面$where等效的$expr查询
db.products.find({ $expr: { $gt: ["$price", { $multiply: ["$cost", 2] }] } })

通常无法使用索引的原因

  • 表达式的动态性$expr支持复杂运算(如字段间的加减乘除、逻辑组合),这些运算结果是动态生成的,而索引是基于字段原始值的有序结构,无法直接匹配运算后的结果。
  • 例外情况:若$expr中仅使用单个字段的简单判断(如$expr: { $eq: ["$status", "active"] }),MongoDB可能会尝试使用该字段的索引。但只要涉及多个字段的运算函数处理(如$substr$year),索引就会失效。

二、实例对比:索引生效 vs 失效

假设有orders集合,包含字段amount(金额)、discount(折扣)、finalPrice(最终价格),且amountdiscount有单字段索引。

1. 索引生效的查询(无$where/$expr
// 查询金额大于1000的订单(使用amount索引)
db.orders.find({ amount: { $gt: 1000 } })
  • 执行计划显示IXSCAN(索引扫描),直接利用amount索引定位符合条件的文档。
2. 索引失效的查询(使用$where/$expr
// 用$where查询"最终价格 = 金额 - 折扣"的订单
db.orders.find({ $where: "this.finalPrice === this.amount - this.discount" })// 用$expr查询同样逻辑
db.orders.find({ $expr: { $eq: ["$finalPrice", { $subtract: ["$amount", "$discount"] }] } })
  • 执行计划显示COLLSCAN(全表扫描),即使amountdiscount有索引,也不会被使用。
  • 原因:MongoDB需要计算每个文档的amount - discount结果,再与finalPrice比较,这个过程无法通过索引加速。

三、如何避免性能问题?

  1. 优先使用字段直接查询:能用普通查询条件(如{ amount: { $gt: 1000 } })实现的逻辑,就避免使用$where$expr

  2. 预计算结果字段:若业务频繁需要字段间运算(如finalPrice = amount - discount),可在文档中新增一个finalPrice字段,存储预计算结果,并为该字段创建索引:

    // 新增预计算字段后,用普通查询(可使用索引)
    db.orders.find({ finalPrice: { $gt: 500 } }) // 假设为finalPrice创建了索引
    
  3. 限制$where/$expr的使用场景:仅在其他方法无法实现时使用,且需配合过滤条件缩小范围(如先通过status: "paid"过滤,再用$expr处理):

    // 先通过索引字段过滤,减少$expr处理的文档数量
    db.orders.find({status: "paid", // 假设status有索引,先过滤出部分文档$expr: { $gt: ["$finalPrice", { $multiply: ["$amount", 0.8] }] }
    })
    

总结

$where$expr的灵活性是以牺牲性能为代价的——它们的动态计算逻辑与索引基于“字段原始值有序存储”的设计理念冲突,因此通常无法利用索引。在实际开发中,应优先通过索引友好的查询方式实现业务逻辑,仅在必要时谨慎使用这两个操作符,并做好性能测试。

http://www.dtcms.com/a/320703.html

相关文章:

  • 使用pybind11封装C++API
  • HTML <picture> 元素:让图片根据设备 “智能切换” 的响应式方案
  • 数据结构(16)排序(上)
  • 时序数据库-涛思数据库
  • 6.Linux 系统上的库文件生成与使用
  • Linux 内核发包流程与路由控制实战
  • 医防融合中心-智慧化慢病全程管理医疗AI系统开发(上)
  • 後端開發技術教學(三) 表單提交、數據處理
  • 排序知识总结
  • 五、mysql8.0在linux中的安装
  • 引领云原生时代,华为云助您构建敏捷未来
  • php防注入和XSS过滤参考代码
  • Orange的运维学习日记--35.DNS拓展与故障排除
  • 31-数据仓库与Apache Hive-Insert插入数据
  • 专利服务系统平台|个人专利服务系统|基于java和小程序的专利服务系统设计与实现(源码+数据库+文档)
  • 代数系统的一般概念与格与布尔代数
  • 云平台运维工具 ——Azure 原生工具
  • 二倍精灵图的做法
  • Jetpack Compose 动画全解析:从基础到高级,让 UI “动” 起来
  • 网络基础——网络层级
  • VSCode 禁用更新检查的方法
  • 并查集算法的一个实战应用详解
  • 基于Flask + Vue3 的新闻数据分析平台源代码+数据库+使用说明,爬取今日头条新闻数据,采集与清洗、数据分析、建立数据模型、数据可视化
  • 认识爬虫 —— 正则表达式提取
  • MySQL数据库操作练习
  • 基于大数据的地铁客流数据分析预测系统 Python+Django+Vue.js
  • css 瀑布流布局
  • 查看泰山派 ov5695研究(1)
  • 线程池基础知识
  • gmssl私钥文件格式