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
1 "_id" : ObjectId("5c42cec9a450b70a55efe01a"), "UserId" : ObjectId("5c429fe2c2462128fccc569b"),
