使用聚合查询汇总数据(续1)
使用 average() 聚合
使用 average() 聚合可返回与给定查询匹配的数值的平均值,例如:
Web
const coll = collection(firestore, 'cities');
const snapshot = await getAggregateFromServer(coll, {
averagePopulation: average('population')
});
console.log('averagePopulation: ', snapshot.data().averagePopulation);
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
let query = db.collection("cities")
let aggregateQuery = query.aggregate([AggregateField.average("population")])
do {
let snapshot = try await aggregateQuery.getAggregation(source: .server)
print(snapshot.get(AggregateField.average("population")))
} catch {
print(error)
}
ViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
FIRQuery *query = [self.db collectionWithPath:@"cities"];
FIRAggregateQuery *aggregateQuery = [query aggregate:@[
[FIRAggregateField aggregateFieldForAverageOfField:@"population"]]];
[aggregateQuery aggregationWithSource:FIRAggregateSourceServer
completion:^(FIRAggregateQuerySnapshot *snapshot,
NSError *error) {
if (error != nil) {
NSLog(@"Error fetching aggregate: %@", error);
} else {
NSLog(@"Avg: %@", [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"population"]]);
}
}];
ViewController.m
Java
Query query = db.collection("cities");
AggregateQuery aggregateQuery = query.aggregate(AggregateField.average("population"));
aggregateQuery.get(AggregateSource.SERVER).addOnCompleteListener(new OnCompleteListener<AggregateQuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<AggregateQuerySnapshot> task) {
if (task.isSuccessful()) {
// Aggregate fetched successfully
AggregateQuerySnapshot snapshot = task.getResult();
Log.d(TAG, "Average: " + snapshot.get(AggregateField.average("population")));
} else {
Log.d(TAG, "Aggregation failed: ", task.getException());
}
}
});
DocSnippets.java
Kotlin
val query = db.collection("cities")
val aggregateQuery = query.aggregate(AggregateField.average("population"))
aggregateQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
if (task.isSuccessful) {
// Aggregate fetched successfully
val snapshot = task.result
Log.d(TAG, "Average: ${snapshot.get(AggregateField.average("population"))}")
} else {
Log.d(TAG, "Aggregate failed: ", task.getException())
}
}
DocSnippets.kt
Dart
db.collection("cities").aggregate(average("population")).get().then(
(res) => print(res.getAverage("population")),
onError: (e) => print("Error completing: $e"),
);
firestore.dart
Java
collection = db.collection("cities");
snapshot = collection.aggregate(average("population")).get().get();
System.out.println("Average: " + snapshot.get(average("population")));
Node.js
const coll = firestore.collection('cities');
const averageAggregateQuery = coll.aggregate({
averagePopulation: AggregateField.average('population'),
});
const snapshot = await averageAggregateQuery.get();
console.log('averagePopulation: ', snapshot.data().averagePopulation);
Python
collection_ref = client.collection("users")
aggregate_query = aggregation.AggregationQuery(collection_ref)
aggregate_query.avg("coins", alias="avg")
results = aggregate_query.get()
for result in results:
print(f"Alias of results from query: {result[0].alias}")
print(f"Average of results from query: {result[0].value}")
Go
func createAvgQuery(w io.Writer, projectID string) error {
ctx := context.Background()
client, err := firestore.NewClient(ctx, projectID)
if err != nil {
return err
}
defer client.Close()
collection := client.Collection("users")
query := collection.Where("born", ">", 1850)
aggregationQuery := query.NewAggregationQuery().WithAvg("coins", "avg_coins")
results, err := aggregationQuery.Get(ctx)
if err != nil {
return err
}
avg, ok := results["avg_coins"]
if !ok {
return errors.New("firestore: couldn't get alias for AVG from results")
}
avgValue := avg.(*firestorepb.Value)
fmt.Fprintf(w, "Avg of results from query: %d\n", avgValue.GetDoubleValue())
return nil
}
average() 聚合会考虑查询的所有过滤条件以及所有限制子句,例如:
Web
const coll = collection(firestore, 'cities');
const q = query(coll, where('capital', '==', true));
const snapshot = await getAggregateFromServer(q, {
averagePopulation: average('population')
});
console.log('averagePopulation: ', snapshot.data().averagePopulation);
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
let query = db.collection("cities").whereField("capital", isEqualTo: true)
let aggregateQuery = query.aggregate([AggregateField.average("population")])
do {
let snapshot = try await aggregateQuery.getAggregation(source: .server)
print(snapshot.get(AggregateField.average("population")))
} catch {
print(error)
}
ViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
FIRQuery *query = [[self.db collectionWithPath:@"cities"]
queryWhereFilter:[FIRFilter filterWhereField:@"capital" isEqualTo:@YES]];
FIRAggregateQuery *aggregateQuery = [query aggregate:@[
[FIRAggregateField aggregateFieldForAverageOfField:@"population"]]];
[aggregateQuery aggregationWithSource:FIRAggregateSourceServer
completion:^(FIRAggregateQuerySnapshot *snapshot,
NSError *error) {
if (error != nil) {
NSLog(@"Error fetching aggregate: %@", error);
} else {
NSLog(@"Avg: %@", [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"population"]]);
}
}];
ViewController.m
Java
Query query = db.collection("cities").whereEqualTo("capital", true);
AggregateQuery aggregateQuery = query.aggregate(AggregateField.average("population"));
aggregateQuery.get(AggregateSource.SERVER).addOnCompleteListener(new OnCompleteListener<AggregateQuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<AggregateQuerySnapshot> task) {
if (task.isSuccessful()) {
// Aggregate fetched successfully
AggregateQuerySnapshot snapshot = task.getResult();
Log.d(TAG, "Average: " + snapshot.get(AggregateField.average("population")));
} else {
Log.d(TAG, "Aggregation failed: ", task.getException());
}
}
});
DocSnippets.java
Kotlin
val query = db.collection("cities").whereEqualTo("capital", true)
val aggregateQuery = query.aggregate(AggregateField.average("population"))
aggregateQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
if (task.isSuccessful) {
// Aggregate fetched successfully
val snapshot = task.result
Log.d(TAG, "Average: ${snapshot.get(AggregateField.average("population"))}")
} else {
Log.d(TAG, "Aggregate failed: ", task.getException())
}
}
DocSnippets.kt
Dart
db
.collection("cities")
.where("capital", isEqualTo: true)
.aggregate(average("population"))
.get()
.then(
(res) => print(res.getAverage("population")),
onError: (e) => print("Error completing: $e"),
);
firestore.dart
Java
collection = db.collection("cities");
query = collection.whereEqualTo("state", "CA");
snapshot = query.aggregate(average("population")).get().get();
System.out.println("Average: " + snapshot.get(average("population")));
Node.js
const coll = firestore.collection('cities');
const q = coll.where("capital", "==", true);
const averageAggregateQuery = q.aggregate({
averagePopulation: AggregateField.average('population'),
});
const snapshot = await averageAggregateQuery.get();
console.log('averagePopulation: ', snapshot.data().averagePopulation);
Python
collection_ref = client.collection("users")
query = collection_ref.where(filter=FieldFilter("people", "==", "Matthew"))
aggregate_query = aggregation.AggregationQuery(query)
aggregate_query.avg("coins", alias="avg")
results = aggregate_query.get()
for result in results:
print(f"Alias of results from query: {result[0].alias}")
print(f"Average of results from query: {result[0].value}")
Go
func createAvgQuery(w io.Writer, projectID string) error {
ctx := context.Background()
client, err := firestore.NewClient(ctx, projectID)
if err != nil {
return err
}
defer client.Close()
collection := client.Collection("users")
query := collection.Where("born", ">", 1850).Limit(5)
aggregationQuery := query.NewAggregationQuery().WithAvg("coins", "avg_coins")
results, err := aggregationQuery.Get(ctx)
if err != nil {
return err
}
avg, ok := results["avg_coins"]
if !ok {
return errors.New("firestore: couldn't get alias for AVG from results")
}
avgValue := avg.(*firestorepb.Value)
fmt.Fprintf(w, "Avg of results from query: %d\n", avgValue.GetDoubleValue())
return nil
}
在查询中计算多项聚合
您可以将多项聚合合并到一个聚合流水线中。这样可以减少所需的索引读取次数。如果查询包含多个字段的聚合,则查询可能需要复合索引。在这种情况下,Cloud Firestore 会建议一个索引。
以下示例在单个聚合查询中执行多次聚合:
Web
const coll = collection(firestore, 'cities');
const snapshot = await getAggregateFromServer(coll, {
countOfDocs: count(),
totalPopulation: sum('population'),
averagePopulation: average('population')
});
console.log('countOfDocs: ', snapshot.data().countOfDocs);
console.log('totalPopulation: ', snapshot.data().totalPopulation);
console.log('averagePopulation: ', snapshot.data().averagePopulation);
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
let query = db.collection("cities")
let aggregateQuery = query.aggregate([
AggregateField.count(),
AggregateField.sum("population"),
AggregateField.average("population")])
do {
let snapshot = try await aggregateQuery.getAggregation(source: .server)
print("Count: \(snapshot.get(AggregateField.count()))")
print("Sum: \(snapshot.get(AggregateField.sum("population")))")
print("Average: \(snapshot.get(AggregateField.average("population")))")
} catch {
print(error)
}
ViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
FIRQuery *query = [self.db collectionWithPath:@"cities"];
FIRAggregateQuery *aggregateQuery = [query aggregate:@[
[FIRAggregateField aggregateFieldForCount],
[FIRAggregateField aggregateFieldForSumOfField:@"population"],
[FIRAggregateField aggregateFieldForAverageOfField:@"population"]]];
[aggregateQuery aggregationWithSource:FIRAggregateSourceServer
completion:^(FIRAggregateQuerySnapshot *snapshot,
NSError *error) {
if (error != nil) {
NSLog(@"Error fetching aggregate: %@", error);
} else {
NSLog(@"Count: %@", [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]]);
NSLog(@"Sum: %@", [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"population"]]);
NSLog(@"Avg: %@", [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"population"]]);
}
}];
ViewController.m
Java
Query query = db.collection("cities");
AggregateQuery aggregateQuery = query.aggregate(
AggregateField.count(),
AggregateField.sum("population"),
AggregateField.average("population"));
aggregateQuery.get(AggregateSource.SERVER).addOnCompleteListener(new OnCompleteListener<AggregateQuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<AggregateQuerySnapshot> task) {
if (task.isSuccessful()) {
// Aggregate fetched successfully
AggregateQuerySnapshot snapshot = task.getResult();
Log.d(TAG, "Count: " + snapshot.get(AggregateField.count()));
Log.d(TAG, "Sum: " + snapshot.get(AggregateField.sum("population")));
Log.d(TAG, "Average: " + snapshot.get(AggregateField.average("population")));
} else {
Log.d(TAG, "Aggregation failed: ", task.getException());
}
}
});
DocSnippets.java
Kotlin
val query = db.collection("cities")
val aggregateQuery = query.aggregate(
AggregateField.count(),
AggregateField.sum("population"),
AggregateField.average("population")
)
aggregateQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
if (task.isSuccessful) {
// Aggregate fetched successfully
val snapshot = task.result
Log.d(TAG, "Count: ${snapshot.get(AggregateField.count())}")
Log.d(TAG, "Sum: ${snapshot.get(AggregateField.sum("population"))}")
Log.d(TAG, "Average: ${snapshot.get(AggregateField.average("population"))}")
} else {
Log.d(TAG, "Aggregate failed: ", task.getException())
}
}
DocSnippets.kt
Dart
db
.collection("cities")
.aggregate(
count(),
sum("population"),
average("population"),
)
.get()
.then(
(res) {
print(res.count);
print(res.getSum("population"));
print(res.getAverage("population"));
},
onError: (e) => print("Error completing: $e"),
);
firestore.dart
Java
collection = db.collection("cities");
query = collection.whereEqualTo("state", "CA");
AggregateQuery aggregateQuery = query.aggregate(count(), sum("population"), average("population"));
snapshot = aggregateQuery.get().get();
System.out.println("Count: " + snapshot.getCount());
System.out.println("Sum: " + snapshot.get(sum("population")));
System.out.println("Average: " + snapshot.get(average("population")));
Node.js
const coll = firestore.collection('cities');
const aggregateQuery = coll.aggregate({
countOfDocs: AggregateField.count(),
totalPopulation: AggregateField.sum('population'),
averagePopulation: AggregateField.average('population')
});
const snapshot = await aggregateQuery.get();
console.log('countOfDocs: ', snapshot.data().countOfDocs);
console.log('totalPopulation: ', snapshot.data().totalPopulation);
console.log('averagePopulation: ', snapshot.data().averagePopulation);
Python
collection_ref = client.collection("users")
query = collection_ref.where(filter=FieldFilter("people", "==", "Matthew"))
aggregate_query = aggregation.AggregationQuery(query)
aggregate_query.sum("coins", alias="sum").avg("coins", alias="avg")
results = aggregate_query.get()
for result in results:
print(f"Alias of results from query: {result[0].alias}")
print(f"Aggregation of results from query: {result[0].value}")
Go
func createMultiAggregationQuery(w io.Writer, projectID string) error {
ctx := context.Background()
client, err := firestore.NewClient(ctx, projectID)
if err != nil {
return err
}
defer client.Close()
collection := client.Collection("users")
query := collection.Where("born", ">", 1850)
aggregationQuery := query.NewAggregationQuery().WithCount("count").WithSum("coins", "sum_coins").WithAvg("coins", "avg_coins")
results, err := aggregationQuery.Get(ctx)
if err != nil {
return err
}
}
具有多项聚合的查询仅包含此类文档,即包含每个聚合中所有字段的文档。这可能会导致单独执行每次聚合时得到不同的结果。
聚合查询的安全规则
Cloud Firestore Security Rules针对聚合查询的工作方式与针对返回文档的查询的工作方式相同。换句话说,当且仅当您的规则允许客户端执行某些集合或集合组查询时,客户端也可以对这些查询执行聚合操作。详细了解 Cloud Firestore Security Rules与查询的相互作用。
行为和限制
在使用聚合查询时,请注意以下行为和限制:
- 您不能将聚合查询与实时监听器和离线查询搭配使用。聚合查询只能通过直接服务器响应进行处理。查询只会通过 Cloud Firestore 后端进行处理,并且会跳过本地缓存和任何已缓冲的更新。此行为与 Cloud Firestore 事务内执行的操作相同。
- 如果聚合无法在 60 秒内完成解析,则会返回
DEADLINE_EXCEEDED错误。聚合性能取决于您的索引配置以及数据集的规模。
如果操作无法在 60 秒时限内完成,作为一种可能的解决方法,您可以针对大型数据集使用计数器。
- 聚合查询会从索引条目中进行读取,并且仅包含编入索引的字段。
- 如果向聚合查询添加 OrderBy 子句,则查询只会对存在排序字段的文档进行计数。
- 对于 sum() 和 average() 聚合,系统会忽略非数字值。sum() 和 average() 聚合仅考虑整数值和浮点值。
- 在单个查询中组合多项聚合时,请注意,sum() 和 average() 会忽略非数字值,而 count() 会包含非数字值。
- 如果您合并针对不同字段的聚合,则计算将仅包含带所有这些字段的文档。
价格
聚合查询的价格取决于查询匹配的索引条目的数量。您需要为大量匹配的条目支付少量读取费用。 对于查询所读取的每批不超过 1,000 个索引条目,系统都会收取相当于一次读取操作的费用。
如需详细了解聚合查询的价格,请参阅聚合查询。
