mongodb的多表聯查與後續的資料處理
阿新 • • 發佈:2018-12-24
背景與簡述
背景
使用nosql作多表操作時很麻煩的,所以平時都沒使用過多表,但最近遇到一個專案必須使用多表,沒法,就研究了一下mongodb的多表聯查功能.
mongodb的多表聯查主要通過聚合完場,使用的是關鍵子unwind則是關鍵的一環.以下是這次的記錄:版本
mongodb:3.6
spring:5.0.7
spring-data:2.0.7
mongo-java-driver:3.6.3
版本問題需要注意一下,如果版本不相容會出現:The ‘cursor’ option is required, except for aggregate…的問題,解決辦法是升級版本,spring-data2.x的執行環境是spring5.x以及jdk8+,mongodb-java-driver也應該升級到3.6以上
資料
- user表
{
"_id" : ObjectId("5b69062240a6d80a6cece003"),
"name" : "小明",
"age" : 28,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.UserBo"
}
- orders表
/* 1 */
{
"_id" : ObjectId("5b69062240a6d80a6cece004"),
"uid" : ObjectId("5b69062240a6d80a6cece003" ),
"money" : 10.0,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"produce" : "產品1",
"_class" : "com.xiangpeng.bo.OrderBo"
}
/* 2 */
{
"_id" : ObjectId("5b6a5711c2eee4295c63768e"),
"uid" : ObjectId("5b69062240a6d80a6cece003"),
"money" : 20.0,
"produce" : "產品2",
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.OrderBo"
}
查詢
- mongodb的多表查詢比較簡單,使用$lookup關鍵字即可:
db.user.aggregate([{$lookup:{from:"orders",localField:"_id",foreignField:"uid",as:"orders"}}])
結果:
{
"_id" : ObjectId("5b69062240a6d80a6cece003"),
"name" : "小明",
"age" : 28,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.UserBo",
"orders" : [
{
"_id" : ObjectId("5b69062240a6d80a6cece004"),
"uid" : ObjectId("5b69062240a6d80a6cece003"),
"money" : 10.0,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"produce" : "產品1",
"_class" : "com.xiangpeng.bo.OrderBo"
},
{
"_id" : ObjectId("5b6a5711c2eee4295c63768e"),
"uid" : ObjectId("5b69062240a6d80a6cece003"),
"money" : 20.0,
"produce" : "產品2",
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.OrderBo"
}
]
}
引數解釋:
form:需要關聯的外表名,$lookup的多變查詢使用的是左外連線
localField:本表的外表關聯欄位;
foreignField:外表的關聯欄位;
as:參考查詢結果,使用$lookup進行查詢後會將所有符合條件的文件封裝為一個list,as引數定義這個list的名字;
資料處理
- 使用$unwind將資料打散:
db.user.aggregate([
{$lookup:{from:"orders",localField:"_id",foreignField:"uid",as:"orders"},
{$unwind:"$orders"}
])
$unwind的作用是將文件中的陣列拆分為多條,拆分結果為:
/* 1 */
{
"_id" : ObjectId("5b69062240a6d80a6cece003"),
"name" : "小明",
"age" : 28,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.UserBo",
"orders" : {
"_id" : ObjectId("5b69062240a6d80a6cece004"),
"uid" : ObjectId("5b69062240a6d80a6cece003"),
"money" : 10.0,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"produce" : "產品1",
"_class" : "com.xiangpeng.bo.OrderBo"
}
}
/* 2 */
{
"_id" : ObjectId("5b69062240a6d80a6cece003"),
"name" : "小明",
"age" : 28,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.UserBo",
"orders" : {
"_id" : ObjectId("5b6a5711c2eee4295c63768e"),
"uid" : ObjectId("5b69062240a6d80a6cece003"),
"money" : 20.0,
"produce" : "產品2",
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.OrderBo"
}
}
資料過濾
現在可以對資料進行過濾,資料過濾的步驟應該儘可能提前,但如果過濾條件中也需要篩選外表條件的話就沒辦法放前面了,過濾在聚合中使用$match
db.user.aggregate([
{$lookup:{from:"orders",localField:"_id",foreignField:"uid",as:"orders"}},
{$unwind:"$orders"},
{$match:{name:"小明","orders.produce":"產品2"}}
])
查出小明買的產品2訂單
結果展示
/* 1 */
{
"_id" : ObjectId("5b69062240a6d80a6cece003"),
"name" : "小明",
"age" : 28,
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.UserBo",
"orders" : {
"_id" : ObjectId("5b6a5711c2eee4295c63768e"),
"uid" : ObjectId("5b69062240a6d80a6cece003"),
"money" : 20.0,
"produce" : "產品2",
"createtime" : ISODate("2018-08-07T02:38:26.601Z"),
"_class" : "com.xiangpeng.bo.OrderBo"
}
}
- 如果對欄位結果有要求可以使用$project進行欄位篩選:
db.user.aggregate([
{$lookup:{from:"orders",localField:"_id",foreignField:"uid",as:"orders"}},
{$unwind:"$orders"},
{$match:{name:"小明","orders.produce":"產品2"}},
{$project:{name:"$name",age:"$age",produce:"$orders.produce",money:"$orders.money"}}])
再聚合中$可以用作引用相應欄位的值
結果為:
/* 1 */
{
"_id" : ObjectId("5b69062240a6d80a6cece003"),
"name" : "小明",
"age" : 28,
"produce" : "產品2",
"money" : 20.0
}
使用spring-data-mongodb在dao層的程式碼
@Repository("aggregateDao")
public class AggregateDaoImpl implements IAggregateDao {
@Autowired
private MongoTemplate mongoTemplate;
public AggregationResults<Document> aggregateLookup() {
// 建立條件
AggregationOperation lookup = Aggregation.lookup("orders", "_id", "uid", "orders");
AggregationOperation unwind = Aggregation.unwind("orders");
AggregationOperation match = Aggregation.match(Criteria.where("name").is("小明").and("orders.produce").is("產品2"));
AggregationOperation project = Aggregation.project("name", "age", "orders.produce", "orders.money");
// 將條件封裝到Aggregate管道
Aggregation aggregation = Aggregation.newAggregation(lookup, unwind, match, project);
// 查詢
AggregationResults<Document> aggregate = mongoTemplate.aggregate(aggregation, "user", Document.class);
return aggregate;
}
}
ps:一些小坑
- $lookup是如果涉及關聯”_id”,注意兩個欄位的型別,用string型別匹配ObjectId型別是沒有結果的~~
- _class欄位也是有些作用的,比如說使用$sum時用作分組
- 資料處理後續:Document返回的值,如果用作前端返回,ObjectId是會被當成BSON解析的~