집계 쿼리로 데이터 요약

집계 쿼리는 여러 색인 항목의 데이터를 처리하여 단일 요약 값을 반환합니다.

Firestore는 다음 집계 쿼리를 지원합니다.

  • count()
  • sum()
  • average()

Firestore는 집계를 계산하고 결과만 앱으로 다시 전송합니다. 앱에서 전체 쿼리를 실행하고 집계를 계산하는 것에 비해 집계 쿼리를 사용하면 요금이 청구되는 문서 읽기와 전송된 바이트 수 모두를 절약할 수 있습니다.

집계 쿼리는 쿼리에서 이미 사용되는 기존 색인 구성을 기반으로 하며 스캔되는 색인 항목 수에 비례하여 확장됩니다. 지연 시간은 집계 항목 개수에 따라 증가합니다.

count() 집계 사용

count() 집계 쿼리를 사용하면 컬렉션이나 쿼리의 문서 수를 결정할 수 있습니다.

데이터 가져오기에서 설정한 예시 데이터를 참조하세요.

다음 count() 집계는 cities 컬렉션의 총 도시 수를 반환합니다.

웹 버전 9

const coll = collection(db, "cities");
const snapshot = await getCountFromServer(coll);
console.log('count: ', snapshot.data().count);
Swift
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
let query = db.collection("cities")
let countQuery = query.count
do {
  let snapshot = try await countQuery.getAggregation(source: .server)
  print(snapshot.count)
} catch {
  print(error)
}
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
FIRCollectionReference *query = [self.db collectionWithPath:@"cities"];
[query.count aggregationWithSource:FIRAggregateSourceServer
                        completion:^(FIRAggregateQuerySnapshot *snapshot,
                                     NSError *error) {
    if (error != nil) {
        NSLog(@"Error fetching count: %@", error);
    } else {
        NSLog(@"Cities count: %@", snapshot.count);
    }
}];
자바
Android
Query query = db.collection("cities");
AggregateQuery countQuery = query.count();
countQuery.get(AggregateSource.SERVER).addOnCompleteListener(new OnCompleteListener<AggregateQuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<AggregateQuerySnapshot> task) {
        if (task.isSuccessful()) {
            // Count fetched successfully
            AggregateQuerySnapshot snapshot = task.getResult();
            Log.d(TAG, "Count: " + snapshot.getCount());
        } else {
            Log.d(TAG, "Count failed: ", task.getException());
        }
    }
});
Kotlin+KTX
Android
val query = db.collection("cities")
val countQuery = query.count()
countQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
    if (task.isSuccessful) {
        // Count fetched successfully
        val snapshot = task.result
        Log.d(TAG, "Count: ${snapshot.count}")
    } else {
        Log.d(TAG, "Count failed: ", task.getException())
    }
}

Dart

// Returns number of documents in users collection
db.collection("users").count().get().then(
      (res) => print(res.count),
      onError: (e) => print("Error completing: $e"),
    );
Go
package firestore

import (
	"context"
	"errors"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
	firestorepb "cloud.google.com/go/firestore/apiv1/firestorepb"
)

func createCountQuery(w io.Writer, projectID string) error {

	// Instantiate the client
	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)

	// `alias` argument--"all"--provides a key for accessing the aggregate query
	// results. The alias value must be unique across all aggregation aliases in
	// an aggregation query and must conform to allowed Document field names.
	//
	// See https://cloud.google.com/firestore/docs/reference/rpc/google.firestore.v1#document for details.
	aggregationQuery := query.NewAggregationQuery().WithCount("all")
	results, err := aggregationQuery.Get(ctx)
	if err != nil {
		return err
	}

	count, ok := results["all"]
	if !ok {
		return errors.New("firestore: couldn't get alias for COUNT from results")
	}

	countValue := count.(*firestorepb.Value)
	fmt.Fprintf(w, "Number of results from query: %d\n", countValue.GetIntegerValue())
	return nil
}
Java
CollectionReference collection = db.collection("cities");
AggregateQuerySnapshot snapshot = collection.count().get().get();
System.out.println("Count: " + snapshot.getCount());
      
Node.js
const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);
      
Python
from google.cloud import firestore
from google.cloud.firestore_v1 import aggregation
from google.cloud.firestore_v1.base_query import FieldFilter

def create_count_query(project_id: str) -> None:
    """Builds an aggregate query that returns the number of results in the query.

    Arguments:
      project_id: your Google Cloud Project ID
    """
    client = firestore.Client(project=project_id)

    collection_ref = client.collection("users")
    query = collection_ref.where(filter=FieldFilter("born", ">", 1800))
    aggregate_query = aggregation.AggregationQuery(query)

    # `alias` to provides a key for accessing the aggregate query results
    aggregate_query.count(alias="all")

    results = aggregate_query.get()
    for result in results:
        print(f"Alias of results from query: {result[0].alias}")
        print(f"Number of results from query: {result[0].value}")

count() 집계에서는 쿼리의 모든 필터와 limit 절을 고려합니다.

웹 버전 9

const coll = collection(db, "cities");
const q = query(coll, where("state", "==", "CA"));
const snapshot = await getCountFromServer(q);
console.log('count: ', snapshot.data().count);
Swift
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
let query = db.collection("cities").whereField("state", isEqualTo: "CA")
let countQuery = query.count
do {
  let snapshot = try await countQuery.getAggregation(source: .server)
  print(snapshot.count)
} catch {
  print(error)
}
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
FIRQuery *query =
    [[self.db collectionWithPath:@"cities"]
                 queryWhereField:@"state"
                       isEqualTo:@"CA"];
[query.count aggregationWithSource:FIRAggregateSourceServer
                        completion:^(FIRAggregateQuerySnapshot *snapshot,
                                      NSError *error) {
    if (error != nil) {
        NSLog(@"Error fetching count: %@", error);
    } else {
        NSLog(@"Cities count: %@", snapshot.count);
    }
}];
자바
Android
Query query = db.collection("cities").whereEqualTo("state", "CA");
AggregateQuery countQuery = query.count();
countQuery.get(AggregateSource.SERVER).addOnCompleteListener(new OnCompleteListener<AggregateQuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<AggregateQuerySnapshot> task) {
        if (task.isSuccessful()) {
            // Count fetched successfully
            AggregateQuerySnapshot snapshot = task.getResult();
            Log.d(TAG, "Count: " + snapshot.getCount());
        } else {
            Log.d(TAG, "Count failed: ", task.getException());
        }
    }
});
Kotlin+KTX
Android
val query = db.collection("cities").whereEqualTo("state", "CA")
val countQuery = query.count()
countQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
    if (task.isSuccessful) {
        // Count fetched successfully
        val snapshot = task.result
        Log.d(TAG, "Count: ${snapshot.count}")
    } else {
        Log.d(TAG, "Count failed: ", task.getException())
    }
}

Dart

// This also works with collectionGroup queries.
db.collection("users").where("age", isGreaterThan: 10).count().get().then(
      (res) => print(res.count),
      onError: (e) => print("Error completing: $e"),
    );
Go
package firestore

import (
	"context"
	"errors"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
	firestorepb "cloud.google.com/go/firestore/apiv1/firestorepb"
)

func createCountQuery(w io.Writer, projectID string) error {

	// Instantiate the client
	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)

	// `alias` argument--"all"--provides a key for accessing the aggregate query
	// results. The alias value must be unique across all aggregation aliases in
	// an aggregation query and must conform to allowed Document field names.
	//
	// See https://cloud.google.com/firestore/docs/reference/rpc/google.firestore.v1#document for details.
	aggregationQuery := query.NewAggregationQuery().WithCount("all")
	results, err := aggregationQuery.Get(ctx)
	if err != nil {
		return err
	}

	count, ok := results["all"]
	if !ok {
		return errors.New("firestore: couldn't get alias for COUNT from results")
	}

	countValue := count.(*firestorepb.Value)
	fmt.Fprintf(w, "Number of results from query: %d\n", countValue.GetIntegerValue())
	return nil
}
Java
CollectionReference collection = db.collection("cities");
Query query = collection.whereEqualTo("state", "CA");
AggregateQuerySnapshot snapshot = query.count().get().get();
System.out.println("Count: " + snapshot.getCount());
      
Node.js
const collectionRef = db.collection('cities');
const query = collectionRef.where('state', '==', 'CA');
const snapshot = await query.count().get();
console.log(snapshot.data().count);
      
Python
from google.cloud import firestore
from google.cloud.firestore_v1 import aggregation
from google.cloud.firestore_v1.base_query import FieldFilter

def create_count_query(project_id: str) -> None:
    """Builds an aggregate query that returns the number of results in the query.

    Arguments:
      project_id: your Google Cloud Project ID
    """
    client = firestore.Client(project=project_id)

    collection_ref = client.collection("users")
    query = collection_ref.where(filter=FieldFilter("born", ">", 1800))
    aggregate_query = aggregation.AggregationQuery(query)

    # `alias` to provides a key for accessing the aggregate query results
    aggregate_query.count(alias="all")

    results = aggregate_query.get()
    for result in results:
        print(f"Alias of results from query: {result[0].alias}")
        print(f"Number of results from query: {result[0].value}")

sum() 집계 사용

sum() 집계를 사용하여 지정된 쿼리와 일치하는 숫자 값의 총 합계를 반환합니다. 예를 들면 다음과 같습니다.

웹 버전 9

const coll = collection(firestore, 'cities');
const snapshot = await getAggregateFromServer(coll, {
  totalPopulation: sum('population')
});

console.log('totalPopulation: ', snapshot.data().totalPopulation);
    
Java
collection = db.collection("cities");
snapshot = collection.aggregate(sum("population")).get().get();
System.out.println("Sum: " + snapshot.get(sum("population")));
  
Node.js
const coll = firestore.collection('cities');
const sumAggregateQuery = coll.aggregate({
         totalPopulation: AggregateField.sum('population'),
       });

const snapshot = await sumAggregateQuery.get();
console.log('totalPopulation: ', snapshot.data().totalPopulation);
      

sum() 집계는 쿼리의 모든 필터와 limit 절을 고려합니다. 예를 들면 다음과 같습니다.

웹 버전 9

const coll = collection(firestore, 'cities');
const q = query(coll, where('capital', '==', true));
const snapshot = await getAggregateFromServer(q, {
  totalPopulation: sum('population')
});

console.log('totalPopulation: ', snapshot.data().totalPopulation);
      
Java
collection = db.collection("cities");
query = collection.whereEqualTo("state", "CA");
snapshot = query.aggregate(sum("population")).get().get();
System.out.println("Sum: " + snapshot.get(sum("population")));
  
Node.js
const coll = firestore.collection('cities');
const q = coll.where("capital", "==", true);
const sumAggregateQuery = q.aggregate({
        totalPopulation: AggregateField.sum('population'),
      });

const snapshot = await sumAggregateQuery.get();
console.log('totalPopulation: ', snapshot.data().totalPopulation);
      

average() 집계 사용

average() 집계를 사용하여 특정 쿼리와 일치하는 숫자 값의 평균을 반환합니다. 예를 들면 다음과 같습니다.

웹 버전 9

const coll = collection(firestore, 'cities');
const snapshot = await getAggregateFromServer(coll, {
  averagePopulation: average('population')
});

console.log('averagePopulation: ', snapshot.data().averagePopulation);
    
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);
      

average() 집계는 쿼리의 모든 필터와 limit 절을 고려합니다. 예를 들면 다음과 같습니다.

웹 버전 9

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);
      
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);
      

쿼리에서 여러 집계 계산

단일 집계 파이프라인에서 여러 집계를 결합할 수 있습니다. 이렇게 하면 필요한 색인 읽기 수가 줄어들 수 있습니다. 쿼리에 여러 필드의 집계가 포함되는 경우 쿼리에 복합 색인이 필요할 수 있습니다. 이 경우 Firestore에서 색인을 제안합니다.

다음 예시는 단일 집계 쿼리에서 여러 집계를 수행합니다.

웹 버전 9

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);
      
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);
      

여러 집계가 있는 쿼리에는 각 집계의 모든 필드가 포함된 문서만 포함됩니다. 이로 인해 각 집계를 개별적으로 수행할 때와 결과가 달라질 수 있습니다.

집계 쿼리에 대한 보안 규칙

Firestore 보안 규칙은 문서를 반환하는 일반 쿼리에서처럼 집계 쿼리에서 동일하게 작동합니다. 즉, 규칙에 따라 클라이언트가 특정 컬렉션 또는 컬렉션 그룹 쿼리를 실행하도록 허용되는 경우에만 클라이언트가 이러한 쿼리에 대해 집계를 수행할 수 있습니다. Firestore 보안 규칙이 쿼리와 상호작용하는 방법을 자세히 알아보세요.

동작 및 제한사항

집계 쿼리를 사용할 때 다음 동작과 제한사항에 주의하세요.

  • 실시간 리스너와 오프라인 쿼리에서는 집계 쿼리를 사용할 수 없습니다. 집계 쿼리는 직접 서버 응답을 통해서만 지원됩니다. 쿼리는 로컬 캐시 및 버퍼링된 업데이트를 건너뛰고 Firestore 백엔드에서만 제공됩니다. 이 동작은 Firestore 트랜잭션 내에서 수행되는 작업과 동일합니다.

  • 집계가 60초 이내에 해결되지 않으면 DEADLINE_EXCEEDED 오류가 반환됩니다. 성능은 색인 구성과 데이터 세트의 크기에 따라 달라집니다.

    60초 기한 내에 작업을 완료할 수 없는 경우 가능한 해결 방법은 대규모 데이터 세트에 카운터를 사용하는 것입니다.

  • 집계 쿼리는 색인 항목에서 읽고 색인이 생성된 필드만 포함합니다.

  • 집계 쿼리에 OrderBy 절을 추가하면 집계가 정렬 필드가 있는 문서로 제한됩니다.

  • sum()average() 집계의 경우 숫자가 아닌 값은 무시됩니다. sum()average() 집계는 정수 값과 부동 소수점 숫자 값만 고려합니다.

  • 단일 쿼리에서 여러 집계를 결합하는 경우 sum()average()는 숫자가 아닌 값을 무시하는 반면 count()에는 숫자가 아닌 값이 포함됩니다.

  • 서로 다른 필드에 있는 집계를 결합하면 해당 필드가 모두 포함된 문서만 계산에 포함됩니다.

가격 책정

집계 쿼리 가격은 쿼리와 일치하는 색인 항목 수에 따라 다릅니다. 일치하는 항목의 수가 많으면 소량의 읽기 요금이 부과됩니다.

자세한 가격 책정 정보를 참조하세요.

다음 단계