MongoDB 聚合(管道與表達式)


  MongoDB中聚合(aggregate)主要用於處理數據(諸如統計平均值,求和等),並返回計算后的數據結果。有點類似sql語句中的 count(*)。

aggregate() 方法

MongoDB中聚合的方法使用aggregate()。

語法

aggregate() 方法的基本語法格式如下所示:

>db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

 

 

下表展示了一些聚合的表達式:

表達式 描述 實例
$sum 計算總和。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])
$avg 計算平均值 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])
$min 獲取集合中所有文檔對應值得最小值。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
$max 獲取集合中所有文檔對應值得最大值。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])
$push 在結果文檔中插入值到一個數組中。 db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])
$addToSet 在結果文檔中插入值到一個數組中,但不創建副本。 db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])
$first 根據資源文檔的排序獲取第一個文檔數據。 db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
$last 根據資源文檔的排序獲取最后一個文檔數據 db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

 

管道的概念

管道在Unix和Linux中一般用於將當前命令的輸出結果作為下一個命令的參數。

MongoDB的聚合管道將MongoDB文檔在一個管道處理完畢后將結果傳遞給下一個管道處理。管道操作是可以重復的。

表達式:處理輸入文檔並輸出。表達式是無狀態的,只能用於計算當前聚合管道的文檔,不能處理其它的文檔。

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

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

 

練習:

數據

> db.user.find()
{ "_id" : ObjectId("5ab8b9495a96a08a5b909000"), "name" : "qlq1", "age" : 20, "se
x" : "" }
{ "_id" : ObjectId("5ab8b9535a96a08a5b909001"), "name" : "qlq2", "age" : 22, "se
x" : "" }
{ "_id" : ObjectId("5ab8b9685a96a08a5b909002"), "name" : "qlq3", "age" : 23, "se
x" : "" }
{ "_id" : ObjectId("5ab8b96e5a96a08a5b909003"), "name" : "qlq4", "age" : 24, "se
x" : "" }

 

 

 

使用方法:使用管道過濾數據之后利用表達式對數據進行操作:

  • 測試分組管道與表達式:  $group   分組統計

 

1.按性別分組,並計算男女人數

db.user.aggregate([
    {$group:{
            _id:"$sex",
            num:{$sum:1}
    }
}])

  

 

   解釋:_id:"$sex"表示按sex屬性分組;  $sum表示求和,如果是$sum:1就相當於count(*),一行記錄算一個

 

2.按性別分組,計算年齡和:

db.user.aggregate([
    {$group:{
            _id:"$sex",
            num:{$sum:"$age"}
    }
}])

 

 

 

 

3.按性別分組,並拿到每個組的第一個年齡:

db.user.aggregate([
    {$group:{
            _id:"$sex",
            num:{$first:"$age"}
    }
}])

 

 

 

 

 4.先按性別分組,分完組之后將age屬性映射到數組中:(相當於分完組之后查看同組的數據,mysql不能實現)

db.user.aggregate([
    {$group:{
            _id:"$sex",
            num:{$push:"$age"}
    }
}])

 

 

 

 

如果是將所有列都添加到數組中用  $push:$$ROOT

 

db.user.aggregate([
    {$group:{
            _id:"$sex",
            num:{$push:"$$ROOT"}
    }
}])

 

結果:

/* 1 */
{
    "_id" : "",
    "num" : [ 
        {
            "_id" : ObjectId("5ab8b9685a96a08a5b909002"),
            "name" : "qlq3",
            "age" : 23.0,
            "sex" : ""
        }, 
        {
            "_id" : ObjectId("5ab8b96e5a96a08a5b909003"),
            "name" : "qlq4",
            "age" : 24.0,
            "sex" : ""
        }
    ]
}

/* 2 */
{
    "_id" : "",
    "num" : [ 
        {
            "_id" : ObjectId("5ab8b9495a96a08a5b909000"),
            "name" : "qlq1",
            "age" : 20.0,
            "sex" : ""
        }, 
        {
            "_id" : ObjectId("5ab8b9535a96a08a5b909001"),
            "name" : "qlq2",
            "age" : 22.0,
            "sex" : ""
        }
    ]
}

 

 

  • $match管道:   類似於find,只是find不能統計,現在是可以過濾並統計

 

1.查詢年齡大學23小於等於50的(只是過濾)

db.user.aggregate([
    {
       $match:{
            age:{$gt:23,$lte:50}
                }
    }
])

 

 

2.在上面過濾的基礎上聚合(先過濾,再分組)

db.user.aggregate([
    {
       $match:{
            age:{$gt:23,$lte:50},
                }
    },
    {
        $group:{
            _id:"$sex",
            num:{
                $sum:1
                }
            }
        }
])

 

 

  • $project   修改輸入文檔的結構。可以用來重命名、增加或刪除域,也可以用於創建計算結果以及嵌套文檔。類似於find方法的第二個參數

 1.查詢年齡大學23小於等於50的,按性別分組並統計人數,並且只取人數列:

 

db.user.aggregate([
    {
       $match:{
            age:{$gt:23,$lte:50},
                }
    },
    {
        $group:{
            _id:"$sex",
            num:{
                $sum:1
                }
            }
        },
     {
         $project:{
             _id:0,
             num:1
             }
         }   
])

 解釋:  $project:{_id:0,num:1}表示在結果中取num列,不取_id列。

 

 

 

  • $sort  排序。類似於sort方法,指定一列並指明排序方式

 1.查詢年齡大於21小於等於35並且按性別分組之后兩列都取,按總數降序排列

db.user.aggregate([
    {
       $match:{
            age:{$gt:21,$lte:50},
                }
    },
    {
        $group:{
            _id:"$sex",
            num:{
                $sum:1
                }
            }
        },
     {
         $project:{
             _id:1,
             num:1
             }
         },
      {
          $sort:{num:-1}
          }   
])

 

 

 

  •  $skip  跳過幾列:     $limit:取幾列

 例如:在上面排序例子的基礎上先跳過1列,取1個值

db.user.aggregate([
    {
       $match:{
            age:{$gt:21,$lte:50},
                }
    },
    {
        $group:{
            _id:"$sex",
            num:{
                $sum:1
                }
            }
        },
     {
         $project:{
             _id:1,
             num:1
             }
         },
      {
          $sort:{num:-1}
          },
       {
           $skip:1
           },
        {
            $limit:1
            }   
])

 

 

 

 例如:在上面排序例子的基礎上先取1列,再跳過1列  (取不到數據)

db.user.aggregate([
    {
       $match:{
            age:{$gt:21,$lte:50},
                }
    },
    {
        $group:{
            _id:"$sex",
            num:{
                $sum:1
                }
            }
        },
     {
         $project:{
             _id:1,
             num:1
             }
         },
      {
          $sort:{num:-1}
          },
       {
           $limit:1
           },
        {
            $skip:1
            }   
])

 

 

 

  • $unwind:將文檔中的某一個數組類型字段拆分成多條,每條包含數組中的一個值。     將數組拆分成一個一個的數據 (相當於分組的逆操作)

 

構造數據:

> db.tshirt.insert({name:'t1',size:90,size:["x","xxx","M"]})
WriteResult({ "nInserted" : 1 })
> db.tshirt.find()
{ "_id" : ObjectId("5ab8c3ce807bacd3133efcf8"), "name" : "t1", "size" : [ "x", "
xxx", "M" ] }
>

 

 

 

例如;按size拆分數據

db.tshirt.aggregate([
   {
       $unwind:'$size'
       }
])

 

 

 

 

對於特殊情況的處理:(非數組,不存在的列,為空數組,為null)

構造數據:

> db.tshirt.find()
{ "_id" : ObjectId("5ab8c5ea807bacd3133efcfd"), "name" : "t1", "price" : 90, "size" : [ "m", "xx", "l" ] }
{ "_id" : ObjectId("5ab8c5f2807bacd3133efcfe"), "name" : "t1", "price" : 90, "size" : [ ] }
{ "_id" : ObjectId("5ab8c608807bacd3133efcff"), "name" : "t3", "price" : 93, "size" : "3x" }
{ "_id" : ObjectId("5ab8c62e807bacd3133efd02"), "name" : "t3", "price" : 93 }
{ "_id" : ObjectId("5ab8c746807bacd3133efd03"), "name" : "t3", "price" : 93, "size" : null }
>

 

 

 1.直接拆分:()

db.tshirt.aggregate([
   {
       $unwind:'$size'
       }
])

 結果:發現數據丟失(字段不存在的和屬性值為null的數據丟失)

 

 

 2.拆分且防止數據丟失

db.tshirt.aggregate([
   {
       $unwind:{
           path:"$size",
           preserveNullAndEmptyArrays:true  #為true表示防止空數組和null丟失
           }
       }
])

 

 

 

 

 

例如:按價格進行分組之后將數據映射到數組中,並按此列拆分數據

 

db.tshirt.aggregate([
    {
        $group:{
                _id:"$price",
                docs:{
                    $push:"$$ROOT"
                    }
            }
        },
    {
        $project:{
                _id:1,
                docs:1
            }
        },
   {
       $unwind:{
           path:"$docs",
           preserveNullAndEmptyArrays:true
           }
       }
])

 

結果:

/* 1 */
{
    "_id" : 93.0,
    "docs" : {
        "_id" : ObjectId("5ab8c608807bacd3133efcff"),
        "name" : "t3",
        "price" : 93.0,
        "size" : "3x"
    }
}

/* 2 */
{
    "_id" : 93.0,
    "docs" : {
        "_id" : ObjectId("5ab8c62e807bacd3133efd02"),
        "name" : "t3",
        "price" : 93.0
    }
}

/* 3 */
{
    "_id" : 93.0,
    "docs" : {
        "_id" : ObjectId("5ab8c746807bacd3133efd03"),
        "name" : "t3",
        "price" : 93.0,
        "size" : null
    }
}

/* 4 */
{
    "_id" : 90.0,
    "docs" : {
        "_id" : ObjectId("5ab8c5ea807bacd3133efcfd"),
        "name" : "t1",
        "price" : 90.0,
        "size" : [ 
            "m", 
            "xx", 
            "l"
        ]
    }
}

/* 5 */
{
    "_id" : 90.0,
    "docs" : {
        "_id" : ObjectId("5ab8c5f2807bacd3133efcfe"),
        "name" : "t1",
        "price" : 90.0,
        "size" : []
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM