说明
aggregate 阶段会根据上一个阶段返回的文档计算汇总结果(例如数量、总和)。
(可选)如果提供了分组表达式,则根据提供的表达式对文档进行分组,然后将累加器函数应用于每个组。
语法
对于没有 group-by 的聚合,aggregate 阶段会采用一个或多个别名聚合器表达式:
Node.js
const cities = await db.pipeline()
.collection("/cities")
.aggregate(
countAll().as("total"),
average("population").as("avg_population")
)
.execute();
对于分组聚合,除了聚合器表达式外,该阶段还接受一个或多个分组键:
Node.js
const result = await db.pipeline()
.collectionGroup('citites')
.aggregate({
accumulators: [
countAll().as('cities'),
field('population').sum().as('total_popoluation')
],
groups: [field('location.state').as('state')]
})
.execute();
行为
无分组的聚合
创建一个包含以下文档的 cities 集合:
Node.js
await db.collection('cities').doc('SF').set({name: 'San Francisco', state: 'CA', country: 'USA', population: 870000});
await db.collection('cities').doc('LA').set({name: 'Los Angeles', state: 'CA', country: 'USA', population: 3970000});
await db.collection('cities').doc('NY').set({name: 'New York', state: 'NY', country: 'USA', population: 8530000});
await db.collection('cities').doc('TOR').set({name: 'Toronto', state: null, country: 'Canada', population: 2930000});
await db.collection('cities').doc('MEX').set({name: 'Mexico City', state: null, country: 'Mexico', population: 9200000});
如需了解城市总数及其平均人口,请执行以下操作:
Node.js
const cities = await db.pipeline()
.collection("/cities")
.aggregate(
countAll().as("total"),
average("population").as("avg_population")
)
.execute();
生成结果如下:
{avg_population: 5100000, total: 5}
对群组执行聚合
通过提供 groups 实参,您可以对每个不同的组执行聚合。
例如,如需查找每个国家/地区和每个州人口最多的城市,请执行以下操作:
Node.js
const cities = await db.pipeline()
.collection("/cities")
.aggregate({
accumulators: [
countAll().as("number_of_cities"),
maximum("population").as("max_population")
],
groups: ["country", "state"]
})
.execute();
生成结果如下:
{country: "USA", state: "CA", max_population: 3970000, number_of_cities: 2},
{country: "USA", state: "NY", max_population: 8530000, number_of_cities: 1},
{country: "Canada", state: null, max_population: 2930000, number_of_cities: 1},
{country: "Mexico", state: null, max_population: 9200000, number_of_cities: 1}
分组依据中的复杂表达式
除了仅按字段值分组之外,aggregate 阶段还支持按复杂表达式的结果分组。任何在 select 阶段有效的表达式都可以用作分组键。这样一来,您就可以根据计算出的值或条件灵活地进行分组。
例如,按州字段是否为 null 进行分组,并计算每个群组的总人口:
Node.js
const cities = await db.pipeline()
.collection("/cities")
.aggregate({
accumulators: [
sum("population").as("total_population")
],
groups: [equal(field("state"), null).as("state_is_null")]
})
.execute();
会返回:
{state_is_null: true, total_population: 12130000}
{state_is_null: false, total_population: 13370000}
聚合器行为
如需了解每种受支持的函数(例如 count、sum、avg)的聚合行为,请参阅聚合函数的专用页面。
分组键行为
对文档进行分组时,Firestore 会使用相等性语义来确定值是否属于同一组。
这意味着,等效值(例如在数学上等效的数值)无论原始类型(32 位整数、64 位整数、浮点数、decimal128 等)如何,都会归为一组。
例如,在集合 numerics 中,假设不同文档的 foo 值分别为 32 位整数 1、64 位整数 1L 和浮点数 1.0,它们都将被聚合到同一个组中。运行按 foo 分组的计数会返回:
{foo: 1.0, count: 3}
如果数据集中存在不同的等效值,则相应组的输出值可以是这些等效值中的任意一个。在此示例中,foo 可以是 1、1L 或 1.0。
即使它看起来是确定性的,您也不应尝试依赖于选择一个特定值的行为。
内存使用量
聚合的执行方式取决于可用的索引。如果查询优化器未选择合适的索引,则聚合需要将所有组缓冲到内存中。
如果组的数量非常多,或者每个组本身非常大(例如,按巨大的值进行分组),此阶段可能会耗尽内存。
在这种情况下,您应应用过滤条件来限制要聚合的数据集,按较小/较少的字段进行分组,或按照建议创建索引,以避免大量使用内存。查询解释将提供有关实际查询执行计划和分析数据的信息,以帮助进行调试。