MongoDB聚合:$bucket

$bucket将输入文档按照指定的表达式和边界进行分组,每个分组为一个文档,称为"桶",每个桶都有一个唯一的_id,其值为文件桶的下线。每个桶中至少要包含一个输入文档,也就是没有空桶。

使用

语法

js 复制代码
{
  $bucket: {
      groupBy: <表达式>,
      boundaries: [ <下边界1>, <下边界2>, ... ],
      default: <literal>,
      output: {
         <output1>: { <$accumulator 表达式> },
         ...
         <outputN>: { <$accumulator 表达式> }
      }
   }
}
groupBy

对文档进行分组的表达式。若指定字段路径,需要在字段名前加上美元符号$并用引号引起来,如:$field_name

除非指定了default,否则所有输入文档的groupBy的值都必须在boundaries指定边界的范围内。

boundaries

分组边界数组,数组中相邻的两个值分别作为桶的上下边界,输入文档根据groupBy表达式的值,确定被分配到哪个桶。数组至少要有两个元素,并按照升序从左到右排列,除数值混合类型外(如:[10, NumberLong(20), NumberInt(30)]),数组元素类型必须一致。

举例:

一个数组 [ 0, 5, 10 ] 创建了两个桶:

0,5),下界为 0,上界为 5。 \[5,10),下界为 5,上界为 10。 ##### default 可选,指定缺省桶的`_id`,不符合`boundaries`范围的文档都会放在缺省桶内。如果不指定`default`,所有输入文档的`groupBy`表达式的值必须落在`boundaries`区间,否则会抛出异常。 缺省值必须小于`boundaries`数组中最小的值或大于`boundaries`数组中的最大值。`default`值的类型可以不同于`boundaries`数组元素的类型。 ##### out 可选,指定输出文档内容中除`_id`字段外要包含的其他字段,指定的字段必须使用汇总(累加器)表达式。 ```js : { : }, ... : { : } ``` 如果未指定`output`文档,默认返回桶内文档数量`count`字段,如果指定了`output`文档的字段,则只返回`_id`和指定的字段,`count`字段默认不会输出。 #### 例子 ##### 按年分桶并对桶的结果进行筛选 创建`artists`集合并插入下面的记录 ```js db.artists.insertMany([ { "_id" : 1, "last_name" : "Bernard", "first_name" : "Emil", "year_born" : 1868, "year_died" : 1941, "nationality" : "France" }, { "_id" : 2, "last_name" : "Rippl-Ronai", "first_name" : "Joszef", "year_born" : 1861, "year_died" : 1927, "nationality" : "Hungary" }, { "_id" : 3, "last_name" : "Ostroumova", "first_name" : "Anna", "year_born" : 1871, "year_died" : 1955, "nationality" : "Russia" }, { "_id" : 4, "last_name" : "Van Gogh", "first_name" : "Vincent", "year_born" : 1853, "year_died" : 1890, "nationality" : "Holland" }, { "_id" : 5, "last_name" : "Maurer", "first_name" : "Alfred", "year_born" : 1868, "year_died" : 1932, "nationality" : "USA" }, { "_id" : 6, "last_name" : "Munch", "first_name" : "Edvard", "year_born" : 1863, "year_died" : 1944, "nationality" : "Norway" }, { "_id" : 7, "last_name" : "Redon", "first_name" : "Odilon", "year_born" : 1840, "year_died" : 1916, "nationality" : "France" }, { "_id" : 8, "last_name" : "Diriks", "first_name" : "Edvard", "year_born" : 1855, "year_died" : 1930, "nationality" : "Norway" } ]) ``` 下面的操作对文档按照`year_born`字段进行分组放入桶中,并根据桶内文档数量进行筛选: ```js db.artists.aggregate( [ // 阶段1 { $bucket: { groupBy: "$year_born", // 分组字段 boundaries: [ 1840, 1850, 1860, 1870, 1880 ], // 桶边界 default: "Other", // 边界外的桶的ID output: { // 指定桶的输出文档 "count": { $sum: 1 }, "artists" : { $push: { "name": { $concat: [ "$first_name", " ", "$last_name"] }, "year_born": "$year_born" } } } } }, // 阶段2 { $match: { count: {$gt: 3} } //过滤出文档数量大于3的桶 } ] ) ``` ##### 阶段1 `$bucket`阶段对文档根据`year_born`分组把文档放入桶,桶的边界为: * \[1840, 1850):下限`1840`(含),上限`1850`(不含)。 * \[1850, 1860):下限`1840`(含),上限`1850`(不含)。 * \[1860, 1870):下限`1840`(含),上限`1850`(不含)。 * \[1870, 1880):下限`1840`(含),上限`1850`(不含)。 * 如果输入文档中`year_born`字段不存在或者值在边界外,文档将被放到`_id`值为`"other"`的缺省桶中。 阶段1的`output`指定了输出文档的字段: | 字段 | 描述 | |---------|----------------------------------------------------------------------------------| | _id | 包含了桶的边界下限 | | count | 桶内文档数量 | | artists | 文档数组,包含了桶内所有文章,每个文档的`artists`字段都包含了拼接后的`first_name`和`last_name`,以及\`year_born'字段 | 通过该阶段后,下面的文档进入下个阶段: ```json { "_id" : 1840, "count" : 1, "artists" : [ { "name" : "Odilon Redon", "year_born" : 1840 } ] } { "_id" : 1850, "count" : 2, "artists" : [ { "name" : "Vincent Van Gogh", "year_born" : 1853 }, { "name" : "Edvard Diriks", "year_born" : 1855 } ] } { "_id" : 1860, "count" : 4, "artists" : [ { "name" : "Emil Bernard", "year_born" : 1868 }, { "name" : "Joszef Rippl-Ronai", "year_born" : 1861 }, { "name" : "Alfred Maurer", "year_born" : 1868 }, { "name" : "Edvard Munch", "year_born" : 1863 } ] } { "_id" : 1870, "count" : 1, "artists" : [ { "name" : "Anna Ostroumova", "year_born" : 1871 } ] } ``` ##### 阶段2 `$match`阶段使用`count>3`的条件,对`$bucket`阶段`out`的文档进行筛选,筛选后的结果如下: ```json { "_id" : 1860, "count" : 4, "artists" : [ { "name" : "Emil Bernard", "year_born" : 1868 }, { "name" : "Joszef Rippl-Ronai", "year_born" : 1861 }, { "name" : "Alfred Maurer", "year_born" : 1868 }, { "name" : "Edvard Munch", "year_born" : 1863 } ] } ``` #### 使用`$bucket`和`$facet`按多个字段分类 使用`$facet`可以在一个阶段执行多个`$bucket`聚合。使用`mongosh`创建`artwork`集合并添加下面的文档: ```js db.artwork.insertMany([ { "_id" : 1, "title" : "The Pillars of Society", "artist" : "Grosz", "year" : 1926, "price" : NumberDecimal("199.99") }, { "_id" : 2, "title" : "Melancholy III", "artist" : "Munch", "year" : 1902, "price" : NumberDecimal("280.00") }, { "_id" : 3, "title" : "Dancer", "artist" : "Miro", "year" : 1925, "price" : NumberDecimal("76.04") }, { "_id" : 4, "title" : "The Great Wave off Kanagawa", "artist" : "Hokusai", "price" : NumberDecimal("167.30") }, { "_id" : 5, "title" : "The Persistence of Memory", "artist" : "Dali", "year" : 1931, "price" : NumberDecimal("483.00") }, { "_id" : 6, "title" : "Composition VII", "artist" : "Kandinsky", "year" : 1913, "price" : NumberDecimal("385.00") }, { "_id" : 7, "title" : "The Scream", "artist" : "Munch", "year" : 1893 /* No price*/ }, { "_id" : 8, "title" : "Blue Flower", "artist" : "O'Keefe", "year" : 1918, "price" : NumberDecimal("118.42") } ]) ``` 下面的操作在一个`$facet`阶段中使用两个`$bucket`,一个使用`price`字段,另一个使用`year`字段分组: ```js db.artwork.aggregate( [ { $facet: { // 顶层 $facet 阶段 "price": [ // 输出字段1 { $bucket: { groupBy: "$price", // 分组字段 boundaries: [ 0, 200, 400 ], // 桶边界数组 default: "Other", // 缺省桶Id output: { // 桶输出内容 "count": { $sum: 1 }, "artwork" : { $push: { "title": "$title", "price": "$price" } }, "averagePrice": { $avg: "$price" } } } } ], "year": [ // 输出字段2 { $bucket: { groupBy: "$year", // 分组字段 boundaries: [ 1890, 1910, 1920, 1940 ], // 桶边界数组 default: "Unknown", // 缺省桶Id output: { // 桶输出内容 "count": { $sum: 1 }, "artwork": { $push: { "title": "$title", "year": "$year" } } } } } ] } } ] ) ``` ##### 方面1 第一个方面按`price`对输入文档进行分组,桶的边界有: * \[0,200),含下限0,不含上限200。 * \[200, 400),含下限200,不含上限400。 * "Other",缺省桶包含了所有不在以上桶内的文档。 `$bucket`阶段的输出`out`文档包含下面的字段: | 字段 | 描述 | |--------------|-----------------------------| | _id | 桶边界下限值 | | count | 桶内文档数量 | | artwork | 包含所有艺术品信息的文档数组 | | averagePrice | 使用`$avg`运算符显示水桶中所有艺术品的平均价格。 | ##### 方面2 第二个方面按`year`对输入文档进行分组,桶的边界有: * \[1890, 1910),含下限1890,不含上限1910。 * \[1910, 1920),含下限1890,不含上限1910。 * \[1920, 1940),含下限1890,不含上限1910。 * "Unknown",缺省桶包含了所有不在以上桶内的文档。 `$bucket`阶段的输出`out`文档包含下面的字段: | 字段 | 描述 | |---------|-----------------| | count | 桶内文档数量 | | artwork | 桶内每件艺术品信息的文件数组。 | ##### 输出 操作返回下面的结果: ```json { "price" : [ // Output of first facet { "_id" : 0, "count" : 4, "artwork" : [ { "title" : "The Pillars of Society", "price" : NumberDecimal("199.99") }, { "title" : "Dancer", "price" : NumberDecimal("76.04") }, { "title" : "The Great Wave off Kanagawa", "price" : NumberDecimal("167.30") }, { "title" : "Blue Flower", "price" : NumberDecimal("118.42") } ], "averagePrice" : NumberDecimal("140.4375") }, { "_id" : 200, "count" : 2, "artwork" : [ { "title" : "Melancholy III", "price" : NumberDecimal("280.00") }, { "title" : "Composition VII", "price" : NumberDecimal("385.00") } ], "averagePrice" : NumberDecimal("332.50") }, { // Includes documents without prices and prices greater than 400 "_id" : "Other", "count" : 2, "artwork" : [ { "title" : "The Persistence of Memory", "price" : NumberDecimal("483.00") }, { "title" : "The Scream" } ], "averagePrice" : NumberDecimal("483.00") } ], "year" : [ // Output of second facet { "_id" : 1890, "count" : 2, "artwork" : [ { "title" : "Melancholy III", "year" : 1902 }, { "title" : "The Scream", "year" : 1893 } ] }, { "_id" : 1910, "count" : 2, "artwork" : [ { "title" : "Composition VII", "year" : 1913 }, { "title" : "Blue Flower", "year" : 1918 } ] }, { "_id" : 1920, "count" : 3, "artwork" : [ { "title" : "The Pillars of Society", "year" : 1926 }, { "title" : "Dancer", "year" : 1925 }, { "title" : "The Persistence of Memory", "year" : 1931 } ] }, { // Includes documents without a year "_id" : "Unknown", "count" : 1, "artwork" : [ { "title" : "The Great Wave off Kanagawa" } ] } ] } ``` ### 注意 跟很多阶段类似,$bucket阶段也有100M内存的限制,缺省情况下如果超出100M将会抛出异常。可使用`allowDiskUse`选项,让聚合管道阶段将数据写入临时文件。

相关推荐
未来之窗软件服务16 分钟前
JAVASCRIPT 前端数据库-V1--仙盟数据库架构-—-—仙盟创梦IDE
数据库·数据库架构·仙盟创梦ide·东方仙盟数据库
LjQ204025 分钟前
网络爬虫一课一得
开发语言·数据库·python·网络爬虫
烙印60129 分钟前
MyBatis原理剖析(二)
java·数据库·mybatis
RestCloud30 分钟前
如何通过ETLCloud实现跨系统数据同步?
数据库·数据仓库·mysql·etl·数据处理·数据同步·集成平台
你是狒狒吗32 分钟前
TM中,return new TransactionManagerImpl(raf, fc);为什么返回是new了一个新的实例
java·开发语言·数据库
Channing Lewis2 小时前
sql server如何创建表导入excel的数据
数据库·oracle·excel
秃头摸鱼侠2 小时前
MySQL安装与配置
数据库·mysql·adb
UGOTNOSHOT2 小时前
每日八股文6.3
数据库·sql
行云流水行云流水2 小时前
数据库、数据仓库、数据中台、数据湖相关概念
数据库·数据仓库
John Song2 小时前
Redis 集群批量删除key报错 CROSSSLOT Keys in request don‘t hash to the same slot
数据库·redis·哈希算法