NodeJs操作MongoDB之多表查询($lookup)与常见问题
一,方法介绍
aggregate()方法来对数据进行聚合操作。aggregate()方法的语法如下
1 aggregate(operators,[options],callback)
operators参数是如表1所示的聚合运算符的数组,它允许你定义对数据执行什么汇总操作。options参数允许你设置readPreference属性,它定义了从哪里读取数据。callback参数是接受err和res
$lookup:可以做多表查询
1 {
2 $lookup://$lookup是如果涉及关联"_id",注意两个字段的类型,用string类型匹配ObjectId类型是没有结果的
3 {
4 from: 'User', // 右集合
5 localField: 'UserId', // 左集合 join 字段 数据类型得统一
6 foreignField: '_id', // 右集合 join 字段 数据类型得统一
7 as: 'fromRole', // 新生成字段(类型array)
8 },
9 },
$match:通过使用query对象运算符来过滤文档集。
1 {$match:{"UserId:'5c429fe2c2462128fccc569b'}}
$unwind:unwind方法会将数组解开,每条包含数组中的一个值。
1 { $unwind: "$fromRole" },//数据打散
与$lookup多表查询一起使用,$加$lookup as 的值(新生成字段)
$project:通过重命名,添加或删除字段重塑文档。你也可以重新计算值,并添加子文档。
1 //以下是包括title并排除name的例子:
2 {$project:{title:1,name:0}}
3 //以下是把name重命名为title的例子:
4 {$project{title:"$name"}}
5 //下面是添加一个新的total字段,并用price和tax字段计算它的值的例子:
6 {$project{total:{$add:["$price","$tax"]}}}
$limit:用来限制MongoDB聚合管道返回的文档数。
1 {$limit:5}//查询五条
$skip:指定处理聚合操作的下一个管道前跳过的一些文档。
和limit()以及skip()的写法也是一样的。
1 { $skip : 5 }//跳过五条 从0开始
$sort:将输入文档排序后输出。
排序指定一个带有field(需要排序的字段名):<sort_order>属性的对象,其中<sort_order>为1表示升序,而-1表示降序
$sort
和我们find()中排序的写法也是一样的。
$group:将集合中的文档分组,可用于统计结果。
把文档分成一组新的文档用于在管道中的下一级。新对象的字段必须在$group对象中定义。
- $addToSet 返回一组文档中所有文档所选字段的全部唯一值的数组。例如:colors:{$addToSet:"color"}
- $first 返回一组文档中一个字段的第一个值。例如:firstValue:{$first:"$value"}
- $last 返回一组文档中一个字段的最后一个值。例如:lastValue:{$last:"$value"}
- $max 返回一组文档中一个字段的最大值。例如:maxValue:{$max:"$value"}
- $min 返回一组文档中一个字段的最小值。例如:minValue:{$min:"$value"}
- $avg 返回一组文档中以个字段的平均值。例如:avgValue:{$avg:"$value"}
- $push 返回一组文档中所有文档所选字段的全部值的数组。例如:username:{$push:"$username"}
- $sum 返回一组文档中以个字段的全部值的总和。例如:total:{$sum:"$value"}
可用在聚合表达式的字符串和算术运算符
- $add:计算数值的总和。例如:valuePlus5:{$add:["$value",5]}
- $divide:给定两个数值,用第一个数除以第二个数。例如:valueDividedBy5:{$divide:["$value",5]}
- $mod:取模。例如:{$mod:["$value",5]}
- $multiply:计算数值数组的乘积。例如:{$multiply:["$value",5]}
- $subtract:给定两个数值,用第一个数减去第二个数。例如:{$subtract:["$value",5]}
- $concat:连接两个字符串 例如:{$concat:["str1","str2"]}
- $strcasecmp:比较两个字符串并返回一个整数来反应比较结果。例如 {$strcasecmp:["$value","$value"]}
- $substr:返回字符串的一部分。例如:hasTest:{$substr:["$value","test"]}
- $toLower:将字符串转化为小写。
- $toUpper:将字符串转化为大写
二,表结构与数据
2.1,用户集合(表)User
1,表结构
1 "User": {
2 "Code": "string",
3 "Name": "string",
4 "Email": "string",
5 "Phone": "string",
6 "Password": "string",
7 "IsEnable": "bool",
8 "LoginTime": "date",
9 "CreateTime": "date",
10 "UpdateTime": "date"
11 },
2,插入的数据
1 {
2 "_id" : ObjectId("5c429fe2c2462128fccc569b"),
3 "Code" : "1234567@qq.com",
4 "Name" : "jackson影琪",
5 "Email" : "123456@qq.com",
6 "Phone" : "15454545454",
7 "Password" : "5f4dcc3b5aa765d61d8327deb882cf99",
8 "IsEnable" : true,
9 "CreateTime" : ISODate("2019-01-19T03:56:18.966Z")
10 }
2.2,角色集合(表)Role
1,表结构
1 "Role": {
2 "Code": "string",
3 "Name": "string",
4 "Description": "string",
5 "CreateTime": "date"
6 },
2,插入的数据
1 {
2 "_id" : ObjectId("5c42cd8fa450b70a55efdf7e"),
3 "Code" : "yingqiRole",
4 "Name" : "yingqi角色",
5 "Description" : "yingqi角色",
6 "CreateTime" : ISODate("2019-01-19T03:56:18.966Z")
7 },
8 {
9 "_id" : ObjectId("5c4564848e297d394920f380"),
10 "Code" : "adminRole",
11 "Name" : "管理员角色",
12 "Description" : "管理员角色",
13 "CreateTime" : ISODate("2019-01-21T03:56:18.966Z")
14 }
2.3,用户与角色关系集合(表)RoleToUser
1,表结构
1 "RoleToUser": {
2 "RoleId": "objectId",//角色表主键_id
3 "UserId": "objectId",//用户表主键_id
4 "CreateTime": "date"
5 }
2,插入的数据
1 {
2 "_id" : ObjectId("5c42cec9a450b70a55efe01a"),
3 "RoleId" : ObjectId("5c42cd8fa450b70a55efdf7e"),
4 "UserId" : ObjectId("5c429fe2c2462128fccc569b"),
5 "CreateTime" : ISODate("2019-01-19T03:56:18.966Z")
6 },
7 {
8 "_id" : ObjectId("5c4564508e297d394920f363"),
9 "RoleId" : ObjectId("5c4564848e297d394920f380"),
10 "UserId" : ObjectId("5c429fe2c2462128fccc569b"),
11 "CreateTime" : ISODate("2019-01-21T03:56:18.966Z")
12 }
三,聚合查询与接口抛出
3.1,聚合查询方法封装
1 /**
2 * 聚合查询 查询多条数据 多表查询
3 * @param table_name 表名
4 * @param pipeLine 管道 [{$lookup: {
5 from:'表名', // 右集合
6 localField: 'UserId', // 左集合 join 字段 数据类型得统一
7 foreignField: '_id', // 右集合 join 字段 数据类型得统一
8 as: 'fromUser', // 新生成字段(类型array)
9 }}
10 ,{$match:{"_id:''}},
11 { $unwind: "$fromUser" },//数据打散
12 ]
13 * @param callback 回调方法
14 */
15 MongoDbAction.queryAggregateMultiTable = function (table_name, pipeLine, callback) {
16 var node_model = this.getConnection(table_name);
17 if (!node_model || node_model.message) {
18 if (callback) callback(1, node_model)
19 } else {
20 node_model.aggregate(pipeLine)
21 .exec(function (err, res) {
22 if (err) {
23 if (callback) callback(err);
24 } else {
25 if (callback) callback(null, res);
26 }
27 });
28 }
29 };
3.2,连接查询并抛出接口
1 //聚合查询数据 多表连接查询 根据用户id获取角色信息
2 router.put('/user/getRoleInfoByUserId', function (req, res) {
3 var tableName = req.body.tableName;//'User'
4 var singleId = req.body.Code;
5 let conditions = {
6 UserId:mongoose.Types.ObjectId(singleId)
7 //_id:{$type:3}
8 }
9 let data = {
10 httpCode: 200,
11 message: "查询成功!",
12 status: 1,
13 data: null,
14 }
15 let pipeLine = [
16 {
17 $lookup:
18 {
19 from: 'User', // 右集合
20 localField: 'UserId', // 左集合 join 字段 数据类型得统一
21 foreignField: '_id', // 右集合 join 字段 数据类型得统一
22 as: 'fromUser', // 新生成字段(类型array)
23 },
24 },
25 {
26 $lookup:
27 {
28 from: 'Role', // 右集合
29 localField: 'RoleId', // 左集合 join 字段 数据类型得统一
30 foreignField: '_id', // 右集合 join 字段 数据类型得统一
31 as: 'fromRole', // 新生成字段(类型array)
32 }
33 },
34 { $match: conditions },
35 { $unwind: "$fromUser" },
36 { $unwind: "$fromRole" },//数据打散
37 ]
38 MongoDbAction.queryAggregateMultiTable(tableName, pipeLine, function (err, result) {
39 if (!err) {
40 data.data = result
41 res.status(data.httpCode).json(data);
42 } else {
43 data.status = 0
44 data.message = "未查询到数据!"
45 data.data = result
46 res.status(data.httpCode).json(data);
47 }
48 });
49 })
3.3,查询结果
查询的条件
查询的结果,已使用unwind方法会将数组解开
四,常见问题
1,$match是如果涉及到"_id",直接传入是没有结果返回的,这是坑1
解决思路:使用aggregate()方法的$match过滤,数据类型必须统一
解决办法:使用mongoose将字符串转成ObjectId,mongoose.Types.ObjectId()方法的使用如下:
1 let conditions = {
2 UserId:mongoose.Types.ObjectId(singleId)//aggregate的$match是如果涉及到"_id",注意字段的类型,如果数据库是ObjectId类型,直接传入是没有结果的,需要将传入的string类型转成ObjectId类型才有结果
3 //_id:{$type:3}
4 }
2,$lookup是如果涉及到"_id",两字段的类型不统一是没有结果返回的,这是坑2
注意两个字段的类型,用string类型匹配ObjectId类型是没有结果的
解决办法:建立集合时与插入数据时,注意类型统一
1 "_id" : ObjectId("5c42cec9a450b70a55efe01a"), "UserId" : ObjectId("5c429fe2c2462128fccc569b"),