天天看點

【mongoDB查詢進階】聚合管道(二) -- 階段操作符https://segmentfault.com/a/1190000010826809什麼是管道操作符(Aggregation Pipeline Operators)管道操作符的分類常用階段操作符階段操作符詳解綜合示例總結

https://segmentfault.com/a/1190000010826809

什麼是管道操作符(Aggregation Pipeline Operators)

mongoDB有4類操作符用于文檔的操作,例如find查詢裡面會用到的$gte,$in等。操作符以$開頭,分為查詢操作符,更新操作符,管道操作符,查詢修飾符4大類。其中管道操作符是用于聚合管道中的操作符。

管道操作符的分類

管道操作符可以分為三類:

  1. 階段操作符(Stage Operators)
  2. 表達式操作符(Expression Operators)
  3. 累加器(Accumulators)

階段操作符是使用于db.collection.aggregate方法裡面,數組參數中的第一層。

db.collection.aggregate( [ { 階段操作符:表述 }, { 階段操作符:表述 }, ... ] )      

表達式操作符主要用于在管道中建構表達式時使用,使用類似于函數那樣需要參數,主要用于$project操作符中,用于建構表達式,使用方法一般如下:

方法1:

{ <operator>: [ <argument1>, <argument2> ... ] }      

方法2:

{ <operator>: <argument> }      

累加器本來隻能使用與$groud下,但是版本3.2或以上,部分累加器還能使用于$project。當在$group中使用時,累加器是針對每個分組使用的;當在$project中使用時,累加器則是針對每個字面量起作用,具體用法下一篇文章闡述。

常用階段操作符

操作符 簡述
$match 比對操作符,用于對文檔集合進行篩選
$project 投射操作符,用于重構每一個文檔的字段,可以提取字段,重命名字段,甚至可以對原有字段進行操作後新增字段
$sort 排序操作符,用于根據一個或多個字段對文檔進行排序
$limit 限制操作符,用于限制傳回文檔的數量
$skip 跳過操作符,用于跳過指定數量的文檔
$count 統計操作符,用于統計文檔的數量
$group 分組操作符,用于對文檔集合進行分組
$unwind 拆分操作符,用于将數組中的每一個值拆分為單獨的文檔
$lookup 連接配接操作符,用于連接配接同一個資料庫中另一個集合,并擷取指定的文檔,類似于populate

階段操作符詳解

假設有一個儲存使用者的集合Users,一個文章的集合Articles,資料大緻如下:

users:

[
    { name: 'John', age: 16, sex: male, city: guangzhou, _id: 1, ...},
    { name: 'Rose', age: 18, sex: female, city: beijing, _id: 2, ...},
    { name: 'Jack', age: 29, sex: male, city: guangzhou, _id: 3, ...},
    { name: 'Allen', age: 18, sex: female, city: beijing, _id: 4, ...},
    { name: 'Cruz', age: 22, sex: male, city: guangzhou, _id: 5, ...},
    { name: 'Peter', age: 18, sex: male, city: guangzhou, _id: 6, ...},
    { name: 'Kelly', age: 23, sex: female, city: shanghai, _id: 7, ...},
    ...
]      

articles:

[
    { title: 'this is article A', author: 'John', _id: 1, ... },
    { title: 'this is article B', author: 'Jack', _id: 2, ... },
    { title: 'this is article C', author: 'Rose', _id: 3, ... },
    { title: 'this is article D', author: 'John', _id: 4, ... },
    { title: 'this is article E', author: 'John', _id: 5, ... },
    ...
]      

$match 比對操作符

說明:
用于重構每一個文檔的字段,可以提取字段,重命名字段,甚至可以對原有字段進行操作後新增字段      
用法:
{ $match: { <query> } }      
示例:
  • 查詢使用者年齡是18歲的使用者
db.users.aggregate([{ $match : { age : "18" } }]);      

$project 投射操作符

用于對文檔集合進行篩選      
{ $project: { <specification(s)> } }      

specification的規則

規則 描述
<字段名>: 1 or true 選擇需要傳回什麼字段
_id: 0 or false 不傳回_id(預設傳回)
<字段名>: 表達式 使用表達式,可以用于重命名字段,或對其值進行操作,或新增字段
<字段名>: 0 or false 選擇需要不傳回什麼字段,注意:當使用這種用法時,就不要用上面的方法
示例1:
  • 使用者集合投射使用者姓名
  • 不傳回_id
db.users.aggregate([{ $project : { name: 1 } }]);      
示例2:
  • 将_id重命名為userId
  • 不傳回_id_
db.users.aggregate([{ $project : { ueserId: '$_id', _id: 0 } }]);      
示例3:
  • 傳回新字段username,并使用表達式讓它的值為name的大寫。
db.users.aggregate([ 
    { 
        $project : {
            name: 1, 
            username: { $toUpper: '$name' }, 
            _id: 0 
        } 
    } 
]);      

關于管道表達式:最簡單的“$project”表達式是包含和排除字段(如: { name: 1 }),以及字段名稱$fieldname(如: { userId: '$_id' })。除此以外,還可以使用表達式操作符(如: $toUpper)構成更豐富的表達式,将多個字面量和變量組合在一起使用,得到更多有意思的值,更多表達式操作符的說明及使用在另外的篇章中詳細闡述。

$sort 排序操作符

用于根據一個或多個字段對文檔進行排序      
用法:
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }      
  • users集合按照年齡age從低到高排序
db.users.aggregate([{ $sort : { age: 1 } }]);      

$limit 限制操作符

用于限制傳回文檔的數量      
{ $limit: <positive integer> }      
  • 傳回5篇article
db.articles.aggregate({ $limit : 3 });      

$skip 跳過操作符

用于跳過指定數量的文檔      
{ $skip: <positive integer> }      
  • 跳過1個文檔
db.users.aggregate([{ $skip : 1 }]);      

$count 統計操作符

用于統計文檔的數量      
{ $count: <string> }      
string是統計之後輸出統計結果的字段名      
  • 統計文章的總數,以totalArticle傳回
db.articles.aggregate([{ totalArticle : 1 }]);      

$group 分組操作符

用于對文檔集合進行分組      
{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }      
_id是必須的,用作分組的依據條件      
  • 将使用者(users)按性别(sex)分組
db.users.aggregate([{ $group : { _id: '$sex' } }]);      

傳回結果:

[
  { _id: 'male' },
  { _id: 'female' }
]      
進階示例:
  • 分組後使用計算各自性别的平均年齡
  • 統計不同的性别的人數,并以count傳回
db.users.aggregate([
    { 
        $group : {
            _id: '$sex', 
            avgAge: { $avg: '$age' }, 
            conut: { $sum: 1 } 
        } 
    }
]);      
[
  { _id: 'male', avgAge: <男性平均年齡>, count: <男性人數> },
  { _id: 'female', avgAge: <女性平均年齡>, count: <女性人數> }
]      
此處用到的表達式 { $avg: '$age' } 用于求平均年齡,$avg是求均值的操作符,$sum用于彙總, 都隻能在$group中使用的累加器,mongoDB3.2以上版本則還可以在$project中使用,詳細會在另外的篇章中闡述。      

$unwind 拆分操作符

用于将數組中的每一個值拆分為單獨的文檔      
{ $unwind: <field path> }      
3.2+版本的用法:
增加icludeArrayIndex,preserveNullAndEmptyArrays兩個可選配置      
{
  $unwind:
    {
      path: <field path>,
      includeArrayIndex: <string>,
      preserveNullAndEmptyArrays: <boolean>
    }
}      
字段 類型
path string 必填,數組的字段名,指定需要拆分的字段
includeArrayIndex 可選,定義傳回的字段名,傳回的值是拆分前值在原數組的位置
preserveNullAndEmptyArrays boolean 可選,配置在path的值為空或缺失的情況下是否拆分, 預設false

假設articles文檔集合是這樣:

{ title: 'this is article A', author: 'John', _id: 1, comments: ['a', 'b', 'c']}      
db.articles.aggregate([{ $unwind: '$comments' }]);      

結果:

[
    { title: 'this is article A', author: 'John', _id: 1, comments: 'a'},
    { title: 'this is article A', author: 'John', _id: 1, comments: 'b'},
    { title: 'this is article A', author: 'John', _id: 1, comments: 'c'},
]      
進階示例(v3.2+):
[
    { title: 'this is article A', author: 'John', _id: 1, comments: ['a', 'b', 'c'] }
    { title: 'this is article B', author: 'Jack', _id: 2 },
    { title: 'this is article C', author: 'Amy', _id: 3, comments: [] },
    { title: 'this is article D', author: 'Lam', _id: 4, comments: null },
]      

操作:

db.articles.aggregate([
    { 
        $unwind: {
            path: '$comments',
            includeArrayIndex: 'arrayIndex',
        }
    }
]);      
[
    { title: 'this is article A', author: 'John', _id: 1, comments: 'a', arrayIndex: NumberLong(0) },
    { title: 'this is article A', author: 'John', _id: 1, comments: 'b', arrayIndex: NumberLong(1) },
    { title: 'this is article A', author: 'John', _id: 1, comments: 'c', arrayIndex: NumberLong(2) },
]      
db.articles.aggregate([
    { 
        $unwind: {
            path: '$comments',
            preserveNullAndEmptyArrays: true,
        }
    }
]);      
[
    { title: 'this is article A', author: 'John', _id: 1, comments: 'a' },
    { title: 'this is article A', author: 'John', _id: 1, comments: 'b' },
    { title: 'this is article A', author: 'John', _id: 1, comments: 'c' },
    { title: 'this is article B', author: 'Jack', _id: 2 },
    { title: 'this is article C', author: 'Amy', _id: 3 },
    { title: 'this is article C', author: 'Amy', _id: 3, comments: null }
]      

$lookup 連接配接操作符

用于連接配接同一個資料庫中另一個集合,并擷取指定的文檔,類似于populate      
{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}      
from 需要關聯的集合名
localField 本集合中需要查找的字段
foreignField 另外一個集合中需要關聯的字段
as 輸出的字段名
  • ariticles中的author關聯到user表
  • authoer字段傳回詳細的使用者的資訊
db.articles.aggregate([
  {
    $lookup:
      {
        from: "users",
        localField: "author",
        foreignField: "name",
        as: "author"
      }
  }
])      
[
    { 
        title: 'this is article A', 
        author: { 
            name: 'John',
            age: 16,
            sex: male,
            city: guangzhou,
            _id: 1, 
            ...
        }, 
        _id: 1, 
        ... 
    },
    { 
        title: 'this is article B', 
        author: { 
            name: 'Jack',
            age: 29,
            sex: male,
            city: guangzhou,
            _id: 3, 
            ...
        }, 
        _id: 2, 
        ... 
    },
    { 
        title: 'this is article C', 
        author: { 
            name: 'Rose',
            age: 18,
            sex: male,
            city: beijing,
            _id: 2, 
            ...
        }, 
        _id: 3, 
        ... 
    },
    { 
        title: 'this is article D', 
        author: { 
            name: 'John',
            age: 16,
            sex: male,
            city: guangzhou,
            _id: 1, 
            ...
        },
        _id: 4, 
        ... 
    },
    { 
        title: 'this is article E', 
        author: { 
            name: 'John',
            age: 16,
            sex: male,
            city: guangzhou,
            _id: 1,
            ...
        },
        _id: 5, 
        ... 
    },
    ...
]      

綜合示例

需求

找出發表文章最多的5位作者,按發表文章排序,顯示他的發表文章的總次數,和他自己的資訊

  • 文章按照作者分組,統計次數
  • 按照次數從高到低排序
  • 截取頭5名
  • 關聯使用者資訊
  • 不輸出文章_id
操作
db.articles.aggregate([
  {
    $group:
      {
        _id: "$author",
        count: { $sum: 1 },
      }
  }, 
  {
        $sort: { count: -1 }
  },
  {
      $skip: 5
  },
  {
      $lookup:
        {
          from: "users",
          localField: "author",
          foreignField: "name",
          as: "author"
        }
  },
  {
      $project: {
          _id: 0,
      }
  }
])      

總結

本文介紹了幾個使用聚合管道查詢時常用的管道操作符的用法,熟練地綜合使用以上操作符可以對資料進行多樣的處理,組合,統計,得出多樣化的資料。另外再加以配合表達式操作符(Expression Operators)組成的表達式, 或者在$project或$group中使用累加器(Accumulators)能查詢統計的内容會更加的多樣化。