MongoDB 原生管道查询

MongoDB 中使用db.COLLECTION_NAME.aggregate([{},...]) 方法来构建和使用聚合管道。先看下官网给的实例,感受一下聚合管道的用法。
Mongoose 高级查询 - 图1

MongoDB Aggregation 管道操作符与表达式

管道操作符 描述
$project 增加、删除、重命名字段
$match 条件匹配
$limit 限制结果的数量
$skip 跳过文档的数量
$sort 条件排序
$group 条件组合结果 统计
$lookup 引入其它集合的数据(表关联查询)

SQL 和 NoSQL 对比

SQL NoSQL
WHERE $match
GROUP BY $group
HAVING $match
SELECT $project
ORDER BY $sort
LIMIT $limit
SUM() $sum
COUNT() $sum
join $lookup

管道表达式

管道操作符作为“键”,所对应的“值”叫做管道表达式。
例如{$match:{status:"A"}}$match称为管道操作符,而status:"A"称为管道表达式,是管道操作符的操作数(Operand)。
每个管道表达式是一个文档结构,它是由字段名、字段值、和一些表达式操作符组成的。

常用表达式操作符 描述
$addToSet 将文档指定字段的值去重
$max 文档指定字段的最大值
$min 文档指定字段的最小值
$sum 文档指定字段求和
$avg 文档指定字段求平均
$gt 大于给定值
$lt 小于给定值
$eq 等于给定值

$project

修改文档的结构,可以用来重命名、增加或删除文档中的字段。

  1. // 要求查找order 只返回文档中trade_no all_price 字段
  2. db.order.aggregate([
  3. {
  4. $project:{
  5. trade_no:1,
  6. all_price:1
  7. }
  8. }
  9. ])

$match

作用: 用于过滤文档。用法类似于find() 方法中的参数。

  1. // 要求查找order 只返回文档中trade_no all_price 字段, 同时只返回价格大于90
  2. db.order.aggregate([
  3. {
  4. $project:{ trade_no:1, all_price:1 }
  5. },
  6. {
  7. $match:{"all_price":{$gte:90}}
  8. }
  9. ])

$group

将集合中的文档进行分组,可用于统计结果。

  1. // 统计每个订单的数量,按照订单号分组
  2. db.order_item.aggregate([
  3. {
  4. $group: {_id: "$order_id", total: {$sum: "$num"}}
  5. }
  6. ])

$sort

将集合中的文档进行排序

  1. db.order.aggregate([
  2. {
  3. $project:{ trade_no:1, all_price:1 }
  4. },
  5. {
  6. $match:{"all_price":{$gte:90}}
  7. },
  8. {
  9. $sort:{"all_price":-1}
  10. }
  11. ])

$limit

限制数量。

  1. db.order.aggregate([
  2. {
  3. $project:{ trade_no:1, all_price:1 }
  4. },
  5. {
  6. $match:{"all_price":{$gte:90}}
  7. },
  8. {
  9. $sort:{"all_price":-1}
  10. },
  11. {
  12. $limit:1
  13. }
  14. ])

$skip

  1. db.order.aggregate([
  2. {
  3. $project:{ trade_no:1, all_price:1 }
  4. },
  5. {
  6. $match:{"all_price":{$gte:90}}
  7. },
  8. {
  9. $sort:{"all_price":-1}
  10. },
  11. {
  12. $skip:1
  13. }
  14. ])

$lookup 表关联

  1. db.order.aggregate([
  2. {
  3. $lookup:{
  4. from: "order_item",
  5. localField: "order_id",
  6. foreignField: "order_id",
  7. as: "items"
  8. }
  9. }
  10. ])

Mongoose 管道查询

模拟数据

设置一个orders 订单集合,一个orderItems 集合,1个订单记录对应多个订单项,构成一对多的关系。

order 的 model

  1. const mongoose = require('./db')
  2. // 利用自动生成的 _id 作为唯一标识
  3. const orderSchema = mongoose.Schema({
  4. allPrice: {
  5. type: Number,
  6. required: true
  7. },
  8. content: String
  9. })
  10. const orderModel = mongoose.model('Order', orderSchema, 'orders')
  11. module.exports = orderModel

orderItem 的 model

  1. const mongoose = require('./db')
  2. const orderItemSchema = mongoose.Schema({
  3. orderID: {
  4. type: mongoose.Schema.Types.ObjectId,
  5. },
  6. title: {
  7. type: String
  8. },
  9. num: Number
  10. })
  11. const orderItemModel = mongoose.model('OrderItem', orderItemSchema, 'orderItem')
  12. module.exports = orderItemModel

创建模拟数据

  1. const OrderModel = require('../model/order')
  2. const OrderItemModel = require('../model/orderItem')
  3. // 模拟数据
  4. const orders = [
  5. { allPrice: 23, content: '订单1'},
  6. { allPrice: 33, content: '订单2'},
  7. { allPrice: 43, content: '订单3'},
  8. { allPrice: 53, content: '订单4'},
  9. { allPrice: 63, content: '订单5'},
  10. { allPrice: 73, content: '订单6'},
  11. { allPrice: 83, content: '订单7'},
  12. ]
  13. const orderItems = [
  14. {title: 'item1', num: 3},
  15. {title: 'item2', num: 3},
  16. {title: 'item3', num: 3},
  17. {title: 'item4', num: 3},
  18. {title: 'item5', num: 3},
  19. {title: 'item6', num: 3},
  20. {title: 'item7', num: 3},
  21. {title: 'item8', num: 3},
  22. ]
  23. // 创建订单,每个订单包含3个随机订单项
  24. const createOrder = (orders, orderItems) => {
  25. orders.forEach((order) => {
  26. let tempOrder = new OrderModel(order)
  27. tempOrder.save((err) => {
  28. if (err) {
  29. console.log('订单创建失败')
  30. return
  31. }
  32. // 创建订单项
  33. let rnd = Math.floor(Math.random()*(orderItems.length-3))
  34. let tempOrderItems = []
  35. tempOrderItems.push(orderItems[rnd])
  36. tempOrderItems.push(orderItems[rnd+1])
  37. tempOrderItems.push(orderItems[rnd+2])
  38. createOrderItems(tempOrderItems, tempOrder._id)
  39. })
  40. })
  41. }
  42. const createOrderItems = (orderItems, orderID) => {
  43. orderItems.forEach(({title, num}) => {
  44. let tempItem = new OrderItemModel({
  45. orderID,
  46. title,
  47. num
  48. })
  49. tempItem.save((err) => {
  50. if (err) {
  51. console.log('订单项保存错误')
  52. return
  53. }
  54. console.log('订单项保存成功')
  55. })
  56. })
  57. }
  58. createOrder(orders, orderItems)

使用管道查询

查询每个订单对应的所有订单项

  1. const OrderModel = require('../model/order')
  2. const OrderItemModel = require('../model/orderItem')
  3. // 查询每个订单的订单项,并保存到 items 的字段中
  4. OrderModel.aggregate([
  5. {
  6. $lookup: {
  7. from: 'orderItem', // 表名
  8. localField: '_id',
  9. foreignField: 'orderID',
  10. as: 'items'
  11. }
  12. }
  13. ], (err, docs) => {
  14. if(err) {
  15. console.log('查询失败')
  16. return
  17. }
  18. docs.forEach((doc) => {
  19. console.log(doc.content)
  20. doc.items.forEach((item) => {
  21. console.log(doc.content, item.title)
  22. })
  23. })
  24. })

查询每个订单对应的每个订单

  1. const OrderModel = require('../model/order')
  2. const OrderItemModel = require('../model/orderItem')
  3. // 查询系念订单项对应的订单内容
  4. OrderItemModel.aggregate([
  5. {
  6. $lookup: {
  7. from: 'orders',
  8. localField: 'orderID',
  9. foreignField: '_id',
  10. as: 'order'
  11. }
  12. }
  13. ], (err, docs) => {
  14. if(err) {
  15. console.log('查询失败')
  16. }
  17. docs.forEach((doc) => {
  18. console.log(`订单项:${doc.title}, 属于订单 ${doc.order[0].content}`)
  19. })
  20. })