MongoDb聚合阶段操作符$bucketAuto学习笔记
功能说明
MongoDb 3.4版本新功能
根据指定的表达式将传入的文档分类到特定数量的组(称为bucket)中。Bucket边界将自动确定,以便将文档平均分配到指定数量的Bucket中。 每个桶在输出中表示为一个文档。每个桶的文档包含一个_id字段,其值指定桶的包含下界和排他上界,以及一个包含桶中文档数量的count字段。在未指定输出时,默认情况下包括count字段。
使用格式
{
$bucketAuto: {
groupBy: <expression>,
buckets: <number>,
output: {
<output1>: { <$accumulator expression> },
...
}
granularity: <string>
}
}
- 字段解释
| 字段 | 类型 | 描述 |
|---|---|---|
| groupBy | expression | 将文档分组的表达式。要指定字段路径,请在字段名前面加上美元符号$并将其括在引号中。 |
| buckets | integer | 一个32位正整数,指定将输入文档分组到其中的桶数。 |
| output | document | 可选值。除_id字段外,指定输出文档中包含的字段的文档。要指定要包含的字段,必须使用累加器表达式 |
| granularity | string | 可选值。指定要使用的首选数字系列的字符串,以确保计算的边界边缘以首选整数或整数的10次方结束。仅在所有groupBy值都是数值且没有NaN时才可用。 |
如果存在下面的情况,则产生的分组数可能会小于指定的桶数
- 输入文档的数量小于指定的桶的数量。
- groupBy表达式的惟一值的数量小于指定的桶的数量。
granularity粒度的间隔比buckets桶的数量少。granularity粒度不够细,无法将文档均匀地分布到指定数量的buckets桶中。
Granularity
$bucketAuto接受一个可选的粒度参数,该参数确保所有bucket的边界符合指定的首选数字序列。使用首选数字系列可以更好地控制在groupBy表达式的值范围中设置桶边界的位置。当groupBy表达式的范围以指数方式扩展时,它们还可以用于帮助以对数方式均匀地设置桶边界。
支持的granularity粒度值有以下几种:
- R5
- R10
- R20
- R40
- R80
- E6
- E12
- E24
- E48
- E96
- E192
- 1-2-5
- POWERSOF2
Renard Series详解
勒纳尔数列是一组通过取10的5次、10次、20次、40次或80次方根得到的数,然后包括根的各种幂,这些幂等于1.0到10.0之间的值(R80是10.3)。 将粒度设置为R5、R10、R20、R40或R80,以将桶边界限制为系列中的值。当groupBy值在1.0到10.0 (R80为10.3)范围之外时,该系列的值乘以10的幂。
例如R5系列以10的五次方根为基础,即1.58,并包含这个根的各种幂(四舍五入),直到达到10。R5系列的推导如下
- 10^0/5^ = 1
- 10^1/5^ = 1.584 ~ 1.6
- 10^2/5^ = 2.511 ~ 2.5
- 10^3/5^ = 3.981 ~ 4.0
- 10^4/5^ = 6.309 ~ 6.3
- 10^5/5^ = 10
创建100个文档,_id从1到100
{ _id: 1 }
{ _id: 2 }
...
{ _id: 100 }
做如下操作
db.things.aggregate( [
{
$bucketAuto: {
groupBy: "$_id",
buckets: 5,
granularity: "R5"
}
}
] )
结果
{
"_id" : {
"min" : 0.63,
"max" : 25.0
},
"count" : 24
}
{
"_id" : {
"min" : 25.0,
"max" : 63.0
},
"count" : 38
}
{
"_id" : {
"min" : 63.0,
"max" : 100.0
},
"count" : 37
}
{
"_id" : {
"min" : 100.0,
"max" : 160.0
},
"count" : 1
}
如果是R10呢?
db.things.aggregate( [
{
$bucketAuto: {
groupBy: "$_id",
buckets: 5,
granularity: "R10"
}
}
] )
结果如下
{
"_id" : {
"min" : 0.8,
"max" : 25.0
},
"count" : 24
}
{
"_id" : {
"min" : 25.0,
"max" : 50.0
},
"count" : 25
}
{
"_id" : {
"min" : 50.0,
"max" : 80.0
},
"count" : 30
}
{
"_id" : {
"min" : 80.0,
"max" : 100.0
},
"count" : 20
}
{
"_id" : {
"min" : 100.0,
"max" : 125.0
},
"count" : 1
}
这些临界值怎么来的?根据R5的推导,R10以10的10次方根为基础,即1.25,并包含这个根的各种幂(四舍五入),直到达到10。R10的推导如下
- 10^0/10^ = 1
- 10^1/10^ = 1.25892541 ~ 1.25
- 10^2/10^ = 1.58489319 ~ 1.6
- 10^3/10^ = 1.99526231 ~ 2.0
- 10^4/10^ = 2.51188643 ~ 2.5
- 10^5/10^ = 3.16227766 ~ 3.16
- 10^6/10^ = 3.98107171 ~ 4.0
- 10^7/10^ = 5.01187234 ~ 5.0
- 10^8/10^ = 6.30957344 ~ 6.3
- 10^9/10^ = 7.94328235 ~ 8.0
因为_id是从1-100,所以1.25扩大100倍即125,也就是最后一个临界值。但是其中的一些数值并没有出现,这里是按什么样的规律来选取其中的数列的呢?
把buckets桶的数量调大为10,再次看R10的结果
{
"_id" : {
"min" : 0.8,
"max" : 12.5
},
"count" : 12
}
{
"_id" : {
"min" : 12.5,
"max" : 25.0
},
"count" : 12
}
{
"_id" : {
"min" : 25.0,
"max" : 40.0
},
"count" : 15
}
{
"_id" : {
"min" : 40.0,
"max" : 50.0
},
"count" : 10
}
{
"_id" : {
"min" : 50.0,
"max" : 63.0
},
"count" : 13
}
{
"_id" : {
"min" : 63.0,
"max" : 80.0
},
"count" : 17
}
{
"_id" : {
"min" : 80.0,
"max" : 100.0
},
"count" : 20
}
{
"_id" : {
"min" : 100.0,
"max" : 125.0
},
"count" : 1
}
这确实增加了R10的数列中的数,再次调整buckets桶的数量,当桶的数量为67,发现最多只能分为18组,之后无论怎么增加,都不会再有多的分组。18组分组结果如下
/* 1 */
{
"_id" : {
"min" : 0.8,
"max" : 1.25
},
"count" : 1
}
/* 2 */
{
"_id" : {
"min" : 1.25,
"max" : 2.5
},
"count" : 1
}
/* 3 */
{
"_id" : {
"min" : 2.5,
"max" : 3.15
},
"count" : 1
}
/* 4 */
{
"_id" : {
"min" : 3.15,
"max" : 5.0
},
"count" : 1
}
/* 5 */
{
"_id" : {
"min" : 5.0,
"max" : 6.3
},
"count" : 2
}
/* 6 */
{
"_id" : {
"min" : 6.3,
"max" : 8.0
},
"count" : 1
}
/* 7 */
{
"_id" : {
"min" : 8.0,
"max" : 10.0
},
"count" : 2
}
/* 8 */
{
"_id" : {
"min" : 10.0,
"max" : 12.5
},
"count" : 3
}
/* 9 */
{
"_id" : {
"min" : 12.5,
"max" : 16.0
},
"count" : 3
}
/* 10 */
{
"_id" : {
"min" : 16.0,
"max" : 20.0
},
"count" : 4
}
/* 11 */
{
"_id" : {
"min" : 20.0,
"max" : 25.0
},
"count" : 5
}
/* 12 */
{
"_id" : {
"min" : 25.0,
"max" : 31.5
},
"count" : 7
}
/* 13 */
{
"_id" : {
"min" : 31.5,
"max" : 40.0
},
"count" : 8
}
/* 14 */
{
"_id" : {
"min" : 40.0,
"max" : 50.0
},
"count" : 10
}
/* 15 */
{
"_id" : {
"min" : 50.0,
"max" : 63.0
},
"count" : 13
}
/* 16 */
{
"_id" : {
"min" : 63.0,
"max" : 80.0
},
"count" : 17
}
/* 17 */
{
"_id" : {
"min" : 80.0,
"max" : 100.0
},
"count" : 20
}
/* 18 */
{
"_id" : {
"min" : 100.0,
"max" : 125.0
},
"count" : 1
}
最终出现的数列中并不完全,原因是1.25到1.99之间不可存在数,_id是从1-100的整数,所以,1.6,2.0,4.0都可以跳过了。
10^0/10^ = 110^1/10^ = 1.25892541 ~ 1.25- 10^2/10^ = 1.58489319 ~ 1.6
- 10^3/10^ = 1.99526231 ~ 2.0
10^4/10^ = 2.51188643 ~ 2.510^5/10^ = 3.16227766 ~ 3.15- 10^6/10^ = 3.98107171 ~ 4.0
10^7/10^ = 5.01187234 ~ 5.010^8/10^ = 6.30957344 ~ 6.310^9/10^ = 7.94328235 ~ 8.0
最终出现的结果如上,其中10^5/10^ = 3.16227766 ~ 3.15按照计算四舍五入应该是3.16,但是MongoDB分组的结果给的却是3.15,
不知道是不是精度已经不影响分组了,MongoDB的计算也就不是采用的精确计算方法
E Series
E数字系列与R系列相似,它们将从1.0到10.0的区间细分为第6、第12、第24、第48、第96或第192个10的平方根,并有特定的相对误差。
- 10^0/6^ = 1
- 10^1/6^ = 1.46779927 ~ 1.5
- 10^2/6^ = 2.15443469 ~ 2.2
- 10^3/6^ = 3.16227766 ~ 3.2
- 10^4/6^ = 4.64158883 ~ 4.6
- 10^5/6^ = 6.81292069 ~ 6.8
- 10^6/6^ = 10
1-2-5 Series
1-2-5系列也是和R系列差不多,只是固定的三个值0.1,0.2,0.5,1,2,5,10,20,50...
buckets桶值为3的结果如下
/* 1 */
{
"_id" : {
"min" : 0.5,
"max" : 50.0
},
"count" : 49
}
/* 2 */
{
"_id" : {
"min" : 50.0,
"max" : 100.0
},
"count" : 50
}
/* 3 */
{
"_id" : {
"min" : 100.0,
"max" : 200.0
},
"count" : 1
}
Powers of Two Series
即指数数列,数列采用2的指数数列区分临界值
- 2^0^ = 1
- 2^1^ = 2
- 2^2^ = 4
- 2^3^ = 8
- 2^4^ = 16 ...
buckets桶值为3的结果
/* 1 */
{
"_id" : {
"min" : 0.5,
"max" : 64
},
"count" : 63
}
/* 2 */
{
"_id" : {
"min" : 64,
"max" : 128
},
"count" : 37
}
注:以上测试数据使用MongoDB version v4.2.2