mongoDB聚合查詢實現

mongo簡介

百度百科上的介紹:MongoDB是一個基於分佈式文件存儲的數據庫。由C++語言編寫。旨在爲WEB應用提供可擴展的高性能數據存儲解決方案。MongoDB 將數據存儲爲一個文檔,數據結構由鍵(key=>value)對組成。MongoDB 文檔類似於 JSON 對象。字段值可以包含其他文檔,數組及文檔數組。

爲啥用捏

其實還是因爲之前做的一個功能,涉及到的數據量比較大,存在數據庫中用sql查詢執行起來很慢很慢,優化之後還是很慢。具體可以參考我寫的那篇sql優化的文章,是的~大概半年前的一個功能了,由於優先級是最低的,所以到現在纔去優化和總結,我是有夠懶的啦。不過最後還是做了,成功將數據從pg轉到mongo了,在實現複雜查詢的時候也是費了一下小勁呢,所以總結下來供以後來回顧一下。

正題

之前的業務數據存入數據庫之後還要關聯其他的表去做一個關聯查詢,由於也沒打算把那些數據再放mongo裏面一份,所以設計數據結構的時候也就把關聯的字段在代碼中處理了一下全都放到一個json裏面了。差不多就那樣子,這樣就不需要設計聯表查詢,只需要對這個collection進行操作就行啦。

{ 
    "_id" : NumberInt(2), 
    "id" : NumberInt(2), 
    "cameraName" : "測試點", //設備名稱
    "clientIp" : "0.0.0.0", 
    "endTime" : NumberLong(20191217102949), 
    "status" : NumberInt(2), //取流狀態  0:正在取流 2 取流結束 1  取流異常
    "retrivalDuration" : NumberLong(200), 
    "createTime" : "2020-02-18 16:46:27", 
    "provinceCode" : "360112", //行政區劃編碼
    "eurl" : "rtsp://10.5.160.103:655/Eurl/qEPs1Km", //短連接
    "longurls" :"rtsp://10.17.10.21:655/0", 
    "clientType" : "BROWSER", //客戶端類型
    "cascadeType" : NumberInt(0),  //("所屬域 0:本級  1:下級  2:上級 3:中間") 
    "userId" : "test", 
    "playType" : "playback", //playback ,realplay 播放類型
    "startTime" : NumberLong(20191217102749), 
    "upCascadeIndexCode" : "36010000002000000088", //上級域編號,
    "downCascadeIndexCode" : "36010000002000000088", //下級域編號
    "cameraIndexCode" : "36011240201310084354", //點位編號
    "sessionId" : "21312xada", //會話id
    "mediaIndexCode" : "3601124020131002321", //媒體編號
    "msgType" : NumberInt(0), //消息類型 0:正常鏈路上報 1:媒體的定時數據校正 2:媒體重啓上報
    "path" : "@100000@360000@360100@360112@", //行政區劃全路徑
    "placeName" : ""//行政區劃名稱
}

聚合查詢

這裏面涉及到什麼操作呢,分頁,模糊查詢,求和函數,分組操作,區間查詢,然後把這些拼到一起~~
一開始用的時候由於對mongo並沒有很瞭解,不知道mongo聚合操作其實是有管道的,代碼裏順序也沒有很注意,導致代碼執行之後沒有報錯但是查詢結果一直不對。這裏簡單說一下管道,也就是aggregation pipeline。
MongoDB的聚合管道將MongoDB文檔在一個管道處理完畢後將結果傳遞給下一個管道處理。管道操作是可以重複的。
表達式:處理輸入文檔並輸出。表達式是無狀態的,只能用於計算當前聚合管道的文檔,不能處理其它的文檔。

這裏我們介紹一下聚合框架中常用的幾個操作:

$project:修改輸入文檔的結構。可以用來重命名、增加或刪除域,也可以用於創建計算結果以及嵌套文檔。
$match:用於過濾數據,只輸出符合條件的文檔。match使用MongoDB的標準查詢操作。
$limit:用來限制MongoDB聚合管道返回的文檔數。
$skip:在聚合管道中跳過指定數量的文檔,並返回餘下的文檔。
$unwind:將文檔中的某一個數組類型字段拆分成多條,每條包含數組中的一個值。
$group:將集合中的文檔分組,可用於統計結果。
$sort:將輸入文檔排序後輸出。
$geoNear:輸出接近某一地理位置的有序文檔。
本次查詢就用到了上面大部分的操作,具體看下面代碼吧:

		Integer page =  resourceRetrivalSearchParamsVO.getPage();
        Integer pageSize = resourceRetrivalSearchParamsVO.getRows();
        Long skip = (page-1) * pageSize.longValue();
        //用來構建條件
        Criteria criteria = new Criteria();
        List<AggregationOperation> operations = new ArrayList<>();
        String name = resourceRetrivalSearchParamsVO.getName();
        String path = resourceRetrivalSearchParamsVO.getProvinceCode();
        if(!StringUtils.isEmpty(name)){
            Pattern namePattern = Pattern.compile(DefaultEnum.CHARACTER_SPOT_STAR.getValue() + name + DefaultEnum.CHARACTER_SPOT_$.getValue(), Pattern.CASE_INSENSITIVE);
            operations.add(Aggregation.match(criteria.where("cameraName").regex(namePattern)));
        }
        if(!StringUtils.isEmpty(path)){
            Pattern pathPattern = Pattern.compile(DefaultEnum.CHARACTER_SPOT_STAR.getValue() + path + DefaultEnum.CHARACTER_SPOT_$.getValue(), Pattern.CASE_INSENSITIVE);
            operations.add(Aggregation.match(criteria.where("path").regex(pathPattern)));
        }

        if(!StringUtils.isEmpty(resourceRetrivalSearchParamsVO.getStartTime())
                &&  !StringUtils.isEmpty(resourceRetrivalSearchParamsVO.getEndTime())){
            //大於方法
            Criteria gt = criteria.where("updateTime").gte(resourceRetrivalSearchParamsVO.getStartTime());
            //小於方法
            Criteria lt = criteria.where("updateTime").lte(resourceRetrivalSearchParamsVO.getEndTime());
            operations.add(Aggregation.match(gt));
            operations.add(Aggregation.match(lt));
        }
        operations.add(Aggregation.group("cameraIndexCode","provinceCode","cameraName","placeName").
                sum("retrivalDuration").as("retrievalDuration").count().as("retrievalNum"));
        operations.add(Aggregation.project("cameraIndexCode","provinceCode","cameraName","placeName","retrievalDuration","retrievalNum"));
        operations.add(Aggregation.sort(Sort.Direction.ASC,"cameraIndexCode"));
        Integer total = mongoTemplate.aggregate(Aggregation.newAggregation(operations),MONGO_COLLECTION_NAME,Map.class)
                .getMappedResults().size();
        operations.add(Aggregation.skip(skip));
        operations.add(Aggregation.limit(pageSize.longValue()));
        Aggregation aggregation = Aggregation.newAggregation(operations);
        List<Map> results = mongoTemplate.aggregate(aggregation,MONGO_COLLECTION_NAME, Map.class).getMappedResults();

具體寫的夠不夠簡潔或者夠不夠優化咱也不知道,但是是可以按照我的需求查詢出數據的。其實拼接這些條件的時候在代碼裏是很抽象的。這裏其實可以合理是用工具的,studio 3T真的是個很好用的工具了,裏面的功能也是很完善,其實對這種拼接條件不是很熟練的話可以把sql寫好,然後去裏面執行一下轉換成聚合查詢語句的,這樣你就知道你需要按什麼順序去拼接這些條件了,具體如圖,這樣效率還是很高的,而且你可以在debug調試的時候把你拼接的查詢語句粘貼出來去工具中一步一步的驗證一下。
studio3T工具

// Requires official MongoShell 3.6+
use local;
db.getCollection("ipdirmgr_resource_retrival_info").aggregate(
    [
        { 
            "$match" : { 
                "start_time" : { 
                    "$gte" : ""
                }, 
                "end_time" : { 
                    "$lte" : ""
                }, 
                "cameraName" : /^\x{6d4b}$/i, 
                "path" : /^$/i
            }
        }, 
        { 
            "$group" : { 
                "_id" : { 
                    "cameraIndexCode" : "$cameraIndexCode", 
                    "cameraName" : "$cameraName", 
                    "provinceCode" : "$provinceCode", 
                    "placeName" : "$placeName"
                }, 
                "SUM(retrivalDuration)" : { 
                    "$sum" : "$retrivalDuration"
                }, 
                "COUNT(cameraIndexCode)" : { 
                    "$sum" : NumberInt(1)
                }
            }
        }, 
        { 
            "$project" : { 
                "cameraIndexCode" : "$_id.cameraIndexCode", 
                "provinceCode" : "$_id.provinceCode", 
                "cameraName" : "$_id.cameraName", 
                "placeName" : "$_id.placeName", 
                "SUM(retrivalDuration)" : "$SUM(retrivalDuration)", 
                "COUNT(cameraIndexCode)" : "$COUNT(cameraIndexCode)", 
                "_id" : NumberInt(0)
            }
        }, 
        { 
            "$skip" : NumberInt(1)
        }, 
        { 
            "$limit" : NumberInt(25)
        }
    ], 
    { 
        "allowDiskUse" : true
    }
);

總結

總之這篇還是偏實用性吧,對mongo具體的原理或者更細化的介紹並沒有,簡單的插入更新之類的操作就不說了,用mongoTemplate實現很簡單。其他的等我先去好好學習一下,後面再來總結吧~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章