1. 程式人生 > >mongodb的多表聯查與後續的資料處理

mongodb的多表聯查與後續的資料處理

背景與簡述

  1. 背景

    使用nosql作多表操作時很麻煩的,所以平時都沒使用過多表,但最近遇到一個專案必須使用多表,沒法,就研究了一下mongodb的多表聯查功能.
    mongodb的多表聯查主要通過聚合完場,使用的是關鍵子lookup,unwind則是關鍵的一環.以下是這次的記錄:

  2. 版本

    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以上

資料

  1. user表
{
    "_id" : ObjectId("5b69062240a6d80a6cece003"),
    "name" : "小明",
    "age" : 28,
    "createtime" : ISODate("2018-08-07T02:38:26.601Z"),
    "_class" : "com.xiangpeng.bo.UserBo"
}
  1. 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" }

查詢

  1. 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:一些小坑

  1. $lookup是如果涉及關聯”_id”,注意兩個欄位的型別,用string型別匹配ObjectId型別是沒有結果的~~
  2. _class欄位也是有些作用的,比如說使用$sum時用作分組
  3. 資料處理後續:Document返回的值,如果用作前端返回,ObjectId是會被當成BSON解析的~