Cloud Firestore 수집 수


답변:


190

업데이트 (2019 년 4 월)-FieldValue.increment (큰 수집 솔루션 참조)


많은 질문과 마찬가지로, 대답은 - 그것은 의존한다 .

프런트 엔드에서 대량의 데이터를 처리 할 때는 매우주의해야합니다. Firestore는 프론트 엔드를 느리게 만드는 것 외에도 백만 번의 읽기 당 0.60 달러를 청구합니다 .


소장 (100 개 미만의 서류)

주의해서 사용-프론트 엔드 사용자 경험에 영향을 줄 수 있음

이 반환 된 배열에 대해 너무 많은 논리를 수행하지 않는 한 프런트 엔드에서이를 처리하는 것이 좋습니다.

db.collection('...').get().then(snap => {
   size = snap.size // will return the collection size
});

중간 수집 (100 ~ 1000 개 문서)

주의해서 사용-Firestore 읽기 호출에는 많은 비용이 소요될 수 있습니다

프런트 엔드에서이를 처리하는 것은 사용자 시스템 속도를 늦출 가능성이 너무 높기 때문에 실현 불가능합니다. 이 논리 서버 측을 처리하고 크기 만 반환해야합니다.

이 방법의 단점은 여전히 ​​Firestore 읽기 (컬렉션 크기와 동일)를 호출하는 것입니다. 장기적으로는 예상보다 많은 비용이 발생할 수 있습니다.

클라우드 기능 :

...
db.collection('...').get().then(snap => {
    res.status(200).send({length: snap.size});
});

프런트 엔드 :

yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
     size = snap.length // will return the collection size
})

대규모 수집 (1000 개 이상의 문서)

가장 확장 가능한 솔루션


FieldValue.increment ()

2019 년 4 월부터 Firestore는 이제 이전의 데이터를 읽지 않고도 카운터를 완전히 원자 적으로 증가시킬 수 있습니다 . 이를 통해 여러 소스에서 동시에 업데이트 할 때 (이전에 트랜잭션을 사용하여 해결 한 경우) 올바른 카운터 값을 유지하면서 수행하는 데이터베이스 읽기 수를 줄일 수 있습니다.


문서의 삭제 또는 생성을 수신함으로써 데이터베이스에있는 카운트 필드에 추가하거나 제거 할 수 있습니다.

firestore 문서- 분산 카운터를 참조 하거나 Jeff Delaney의 데이터 집계 를 살펴보십시오 . 그의 가이드는 AngularFire를 사용하는 모든 사람에게 정말 환상적이지만 그의 교훈은 다른 프레임 워크에도 적용됩니다.

클라우드 기능 :

export const documentWriteListener = 
    functions.firestore.document('collection/{documentUid}')
    .onWrite((change, context) => {

    if (!change.before.exists) {
        // New document Created : add one to count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(1)});

    } else if (change.before.exists && change.after.exists) {
        // Updating existing document : Do nothing

    } else if (!change.after.exists) {
        // Deleting document : subtract one from count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(-1)});

    }

return;
});

이제 프론트 엔드에서이 numberOfDocs 필드를 쿼리하여 컬렉션의 크기를 얻을 수 있습니다.


17
대규모 컬렉션을위한 훌륭한 솔루션! 구현자가 읽기를 래핑하고 firestore.runTransaction { ... }블록으로 작성해야한다는 것을 추가하고 싶습니다 . 이 액세스에 동시성 문제를 해결합니다 numberOfDocs.
— efemoney

2
이 방법들은 많은 수의 레코드를 사용하고 있습니다. 카운터를 사용하고 트랜잭션을 사용하여 카운터를 늘리면 추가 비용과 클라우드 기능이 필요하지 않은 동일한 결과를 얻지 못합니까?
— user3836415

10
대규모 컬렉션에 대한 솔루션은 dem 등성이 없으며 어떤 규모에서도 작동하지 않습니다. Firestore 문서 트리거는 적어도 한 번 실행되지만 여러 번 실행될 수 있습니다. 이런 일이 발생하면 트랜잭션 내부에서 업데이트를 유지 관리하더라도 두 번 이상 실행될 수 있으므로 잘못된 숫자가 표시됩니다. 이 작업을 시도했을 때 한 번에 12 개 미만의 문서 작성 문제가 발생했습니다.
— Tym Pollack

2
@TymPollack 님 안녕하세요. 클라우드 트리거를 사용하여 일관되지 않은 동작을 발견했습니다. 당신이 경험 한 행동을 설명하기 위해 저를 기사 나 포럼에 연결할 수 있습니까?
— Matthew Mullin

2
@cmprogram db.collection ( '...')을 사용할 때 전체 컬렉션과 데이터를 읽고 있습니다. 따라서 데이터가 필요하지 않으면 맞습니다-쉽게 목록을 요청할 수 있습니다 컬렉션 ID (컬렉션 문서 데이터가 아님)를 읽은 것으로 간주합니다.
— atereshkov

25

가장 간단한 방법은 "querySnapshot"의 크기를 읽는 것입니다.

db.collection("cities").get().then(function(querySnapshot) {      
    console.log(querySnapshot.size); 
});

"querySnapshot"내 문서 배열의 길이를 읽을 수도 있습니다.

querySnapshot.docs.length;

또는 빈 값을 읽어 "querySnapshot"이 비어 있으면 부울 값이 반환됩니다.

querySnapshot.empty;

73
각 문서는 한 번의 읽기 비용을 지불합니다. 따라서이 방법으로 컬렉션에서 100 개의 항목을 세면 100 번의 읽기 비용이 청구됩니다!
— Georg

맞지만 컬렉션의 문서 수를 요약하는 다른 방법은 없습니다. 컬렉션을 이미 가져온 경우 "docs"배열을 읽을 때 더 이상 페치를 필요로하지 않으므로 더 많은 판독 값을 "비용"으로 사용하지 않아도됩니다.
— Ompel

5
메모리의 모든 문서를 읽습니다! 대규모 데이터 세트에 대한 그 행운을 빌어 ...
— 댄 Dascalescu

85
Firebase Firestore에는 종류가 없다는 것이 정말 믿기지 않습니다 db.collection.count(). 생각 만이 그들을 포기
— 블루 봇

8
특히 대규모 컬렉션의 경우 실제로 모든 문서를 다운로드하여 사용한 것처럼 청구하는 것은 불공평합니다. 테이블 (컬렉션) 수는 기본 기능입니다. 가격 모델과 Firestore가 2017 년에 출시되었다는 점을 고려할 때 Google이 컬렉션 크기를 얻는 다른 방법을 제공하지 않는 것은 놀라운 일입니다. 그들이 그것을 구현하지 않을 때까지, 그들은 적어도 그것을 청구하지 않아야합니다.
— nibbana

23

내가 아는 한 여기에는 내장 솔루션이 없으며 노드 sdk에서만 가능합니다. 당신이 있다면

db.collection('someCollection')

당신이 사용할 수있는

.select([fields])

선택할 필드를 정의합니다. 빈 select ()를 수행하면 문서 참조 배열을 얻을 수 있습니다.

예:

db.collection('someCollection').select().get().then( (snapshot) => console.log(snapshot.docs.length) );

이 솔루션은 모든 문서를 다운로드하는 최악의 경우를위한 최적화 일 뿐이며 대규모 컬렉션에는 적용되지 않습니다!

또한 이것 좀 봐 :
클라우드 경우 FireStore와 컬렉션의 문서 수의 수를 얻는 방법


내 경험에 따르면select(['_id'])select()
— JAnton

13

큰 컬렉션 의 문서 수를 세어주의하십시오 . 모든 컬렉션에 대해 미리 계산 된 카운터를 원하면 firestore 데이터베이스와 약간 복잡합니다.

이 경우 다음과 같은 코드가 작동하지 않습니다.

export const customerCounterListener = 
    functions.firestore.document('customers/{customerId}')
    .onWrite((change, context) => {

    // on create
    if (!change.before.exists && change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count + 1
                     }))
    // on delete
    } else if (change.before.exists && !change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count - 1
                     }))
    }

    return null;
});

https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees : Firestore 문서에서 알 수 있듯이 모든 클라우드 Firestore 트리거는 dem 등원이어야하기 때문입니다.

해결책

따라서 코드가 여러 번 실행되지 않도록하려면 이벤트 및 트랜잭션으로 관리해야합니다. 이것은 대규모 수집 카운터를 처리하는 특별한 방법입니다.

const executeOnce = (change, context, task) => {
    const eventRef = firestore.collection('events').doc(context.eventId);

    return firestore.runTransaction(t =>
        t
         .get(eventRef)
         .then(docSnap => (docSnap.exists ? null : task(t)))
         .then(() => t.set(eventRef, { processed: true }))
    );
};

const documentCounter = collectionName => (change, context) =>
    executeOnce(change, context, t => {
        // on create
        if (!change.before.exists && change.after.exists) {
            return t
                    .get(firestore.collection('metadatas')
                    .doc(collectionName))
                    .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: ((docSnap.data() && docSnap.data().count) || 0) + 1
                        }));
        // on delete
        } else if (change.before.exists && !change.after.exists) {
            return t
                     .get(firestore.collection('metadatas')
                     .doc(collectionName))
                     .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: docSnap.data().count - 1
                        }));
        }

        return null;
    });

사용 사례는 다음과 같습니다.

/**
 * Count documents in articles collection.
 */
exports.articlesCounter = functions.firestore
    .document('articles/{id}')
    .onWrite(documentCounter('articles'));

/**
 * Count documents in customers collection.
 */
exports.customersCounter = functions.firestore
    .document('customers/{id}')
    .onWrite(documentCounter('customers'));

보다시피, 다중 실행을 방지하는 핵심 은 컨텍스트 객체에서 eventId 라는 속성 입니다. 동일한 이벤트에 대해 함수가 여러 번 처리 된 경우 이벤트 ID는 모든 경우에 동일합니다. 불행히도 데이터베이스에는 "이벤트"컬렉션이 있어야합니다.


2
그들은이 동작이 1.0 릴리스에서 수정 된 것처럼 이것을 표현하고 있습니다. Amazon AWS 기능에도 동일한 문제가 있습니다. 필드를 계산하는 것만 큼 간단하고 복잡합니다.
— MarcG

더 나은 솔루션처럼 보이기 때문에 지금 시도해보십시오. 돌아가서 이벤트 컬렉션을 삭제 하시겠습니까? 날짜 필드를 추가하고 하루보다 오래된 데이터를 삭제하거나 데이터 세트를 작게 유지하기 위해 (1mil + 이벤트 / 일) 가능성을 생각하고있었습니다. FS에서 그렇게하는 쉬운 방법이 없다면 ... 몇 달만 FS를 사용하고있었습니다.
— Tym Pollack

1
context.eventId동일한 트리거를 여러 번 호출 할 때 항상 동일한 지 확인할 수 있습니까 ? 테스트에서 일관성이있는 것으로 보이지만이를 나타내는 "공식"문서를 찾을 수 없습니다.
— Mike McLin

2
그래서 이것을 잠시 사용한 후에,이 솔루션은 정확히 하나의 쓰기로 작동하지만, 너무 많은 트리거가 한 번에 여러 문서에서 작성되고 동일한 카운트 문서를 업데이트하려고 시도하면 훌륭합니다. firestore에서 경합 오류가 발생합니다. 당신은 그 문제를 겪었습니까? (오류 : 10 ABORTED :이 문서에 대한 논쟁이 너무 많습니다. 다시 시도하십시오.)
— Tym Pollack

1
@TymPollack 분산 카운터를 보면 문서 쓰기가 초당 약 한 번의 업데이트로 제한됩니다.
— Jamie

8

2020 년에는 여전히 Firebase SDK에서 사용할 수 없지만 Firebase 확장 (베타) 에서는 사용할 수 있지만 설정 및 사용이 매우 복잡합니다.

합리적인 접근

도우미 ... (만들기 / 삭제가 중복 된 것처럼 보이지만 onUpdate보다 저렴함)

export const onCreateCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(1);
  await statsDoc.set(countDoc, { merge: true });
};

export const onDeleteCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(-1);
  await statsDoc.set(countDoc, { merge: true });
};

export interface CounterPath {
  watch: string;
  name: string;
}

Firestore 후크 내보내기


export const Counters: CounterPath[] = [
  {
    name: "count_buildings",
    watch: "buildings/{id2}"
  },
  {
    name: "count_buildings_subcollections",
    watch: "buildings/{id2}/{id3}/{id4}"
  }
];


Counters.forEach(item => {
  exports[item.name + '_create'] = functions.firestore
    .document(item.watch)
    .onCreate(onCreateCounter());

  exports[item.name + '_delete'] = functions.firestore
    .document(item.watch)
    .onDelete(onDeleteCounter());
});

행동

건물 루트 수집 및 모든 하위 수집 이 추적됩니다.

여기에 이미지 설명을 입력하십시오

여기 /counters/루트 경로 아래

여기에 이미지 설명을 입력하십시오

이제 수집 횟수가 자동으로 업데이트됩니다! 카운트가 필요한 경우 컬렉션 경로를 사용하고 접두사를 붙입니다 counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));

"초당 1 개의 문서 업데이트"와 동일한 제한이 적용되지 않습니까?
— Ayyappa

예, 그러나 결국 일관성이 있습니다. 즉, 수집 횟수는 결국 실제 수집 카운트 정렬을, 그것을 구현하는 가장 쉬운 해결책 많은 경우에 카운트 짧은 지연이 허용됩니다.
— 벤 와인딩

7

@Matthew에 동의 합니다. 이러한 쿼리를 수행하면 비용이 많이 듭니다 .

[프로젝트를 시작하기 전에 개발자를위한 조언]

우리는이 상황을 처음에 예상 했으므로 실제로 모든 카운터를 type 필드에 저장하기 위해 문서와 함께 카운터를 만들 수 있습니다 number.

예를 들면 다음과 같습니다.

콜렉션의 각 CRUD 조작에 대해 카운터 문서를 업데이트하십시오.

  1. 새로운 컬렉션 / 서브 컬렉션 을 생성 할 때 : (카운터에서 +1) [1 쓰기 작업]
  2. 컬렉션 / 서브 컬렉션 을 삭제 하는 경우 : (카운터에서 -1) [1 쓰기 작업]
  3. 기존 수집 / 하위 수집 을 업데이트 할 때는 카운터 문서에서 아무것도하지 마십시오. (0)
  4. 때를 기존 수집 / 하위 수집 읽을 카운터 문서에서 아무것도하지 마십시오 : (0)

다음에 컬렉션 수를 얻으려면 문서 필드를 쿼리하거나 가리켜 야합니다. [1 회 읽기 동작]

또한 컬렉션 이름을 배열에 저장할 수 있지만 까다로울 수 있습니다. firebase의 배열 조건은 다음과 같습니다.

// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}

// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']

// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}

따라서 컬렉션을 삭제하지 않으려는 경우 실제로 모든 컬렉션을 쿼리 할 때마다 배열을 사용하여 컬렉션 목록 이름을 저장할 수 있습니다.

그것이 도움이되기를 바랍니다!


작은 컬렉션의 경우 그러나 Firestore 문서 크기 제한은 ~ 1MB입니다. 모음의 문서 ID가 자동 생성 된 경우 (20 바이트), 배열을 보유한 문서 전에 ~ 52,425 개만 저장할 수 있습니다. 너무 큽니다. 50,000 요소마다 새 문서를 만들 수 있지만 그 배열을 유지 관리하는 것은 완전히 관리 할 수 ​​없다는 해결 방법으로 생각합니다. 또한, 문서 크기가 커짐에 따라 읽고 업데이트하는 데 시간이 오래 걸리므로 결국 다른 작업이 시간 초과 될 수 있습니다.
— Tym Pollack

5

아니요, 현재 집계 쿼리를 기본적으로 지원하지 않습니다. 그러나 할 수있는 일이 몇 가지 있습니다.

첫 번째는 여기 에 문서화되어 있습니다 . 트랜잭션 또는 클라우드 기능을 사용하여 집계 정보를 유지 보수 할 수 있습니다.

이 예는 함수를 사용하여 하위 등급의 등급 수와 평균 등급을 추적하는 방법을 보여줍니다.

exports.aggregateRatings = firestore
  .document('restaurants/{restId}/ratings/{ratingId}')
  .onWrite(event => {
    // Get value of the newly added rating
    var ratingVal = event.data.get('rating');

    // Get a reference to the restaurant
    var restRef = db.collection('restaurants').document(event.params.restId);

    // Update aggregations in a transaction
    return db.transaction(transaction => {
      return transaction.get(restRef).then(restDoc => {
        // Compute new number of ratings
        var newNumRatings = restDoc.data('numRatings') + 1;

        // Compute new average rating
        var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
        var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;

        // Update restaurant info
        return transaction.update(restRef, {
          avgRating: newAvgRating,
          numRatings: newNumRatings
        });
      });
    });
});

jbb가 언급 한 솔루션은 문서를 자주 계산하지 않으려는 경우에도 유용합니다. select()각 문서를 모두 다운로드하지 않도록 명령문 을 사용하십시오 (카운트 만 필요할 때 많은 대역폭이 필요함). select()솔루션은 모바일 앱에서 작동하지 않도록 지금은 서버 SDK에서만 사용할 수 있습니다.


1
이 솔루션은 dem 등성이 아니므로 두 번 이상 실행되는 트리거는 여러 등급과 평균을 버립니다.
— Tym Pollack

4

사용 가능한 직접 옵션이 없습니다. 당신은 할 수 없습니다 db.collection("CollectionName").count(). 다음은 컬렉션 내 문서 수를 찾는 두 가지 방법입니다.

1 :-컬렉션의 모든 문서를 가져온 다음 크기를 얻습니다. (최상의 해결책은 아님)

db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})

위의 코드를 사용하면 문서 읽기는 컬렉션 내의 문서 크기와 동일하므로 위의 솔루션을 사용하지 않아야하는 이유입니다.

2 :-컬렉션에있는 문서 수를 저장할 컬렉션에 별도의 문서를 만듭니다 (최상의 솔루션).

db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})

위에서 우리는 모든 개수 정보를 저장하기 위해 이름 개수를 가진 문서를 만들었습니다. 다음과 같은 방법으로 개수 문서를 업데이트 할 수 있습니다.

  • 문서 수에 대한 Firestore 트리거 생성
  • 새 문서를 만들 때 카운트 문서의 개수 속성을 증가시킵니다.
  • 문서가 삭제 될 때 개수 문서의 개수 속성을 줄입니다.

wrt 가격 (문서 읽기 = 1) 및 빠른 데이터 검색 위의 솔루션이 좋습니다.


3

admin.firestore.FieldValue.increment를 사용하여 카운터를 늘리십시오 .

exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onCreate((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(1),
    })
  );

exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onDelete((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(-1),
    })
  );

이 예에서는 instanceCount문서가 문서에 추가 될 때마다 프로젝트 의 필드를 증가시킵니다 .instances 하위 컬렉션에 . 필드가 아직 없으면 필드가 작성되어 1로 증가합니다.

증분은 내부적으로 거래되지만 분산 카운터를 사용해야합니다 1 초마다 더 자주 증분해야하는 경우 .

그것은 구현하는 것이 바람직의 onCreate과 onDelete보다는 onWrite으로 전화 할 것 onWrite(당신이 당신의 컬렉션의 문서를 업데이트 할 경우) 불필요한 함수 호출에 더 많은 돈을 지출 의미하는 업데이트를.


2

해결 방법은 다음과 같습니다.

firebase 문서에 카운터를 작성하십시오. 새 항목을 만들 때마다 거래 내에서 증가합니다.

새 항목의 필드에 개수를 저장합니다 (예 : 위치 : 4).

그런 다음 해당 필드에 인덱스를 작성하십시오 (위치 DESC).

쿼리로 건너 뛰기 + 제한을 수행 할 수 있습니다 .Where ( "position", "<"x) .OrderBy ( "position", DESC)

도움이 되었기를 바랍니다!


1

이 모든 아이디어를 사용하여 모든 카운터 상황을 처리하는 범용 함수를 만들었습니다 (쿼리 제외).

한 번만 너무 많은 쓰기 작업을 수행하면 속도가 느려집니다. 예를 들어 인기 게시물의 좋아요가 표시 됩니다. 예를 들어 블로그 게시물에서 과잉이며 더 많은 비용이 듭니다. 이 경우 샤드를 사용하여 별도의 기능을 만드는 것이 좋습니다. https://firebase.google.com/docs/firestore/solutions/counters

// trigger collections
exports.myFunction = functions.firestore
    .document('{colId}/{docId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// trigger sub-collections
exports.mySubFunction = functions.firestore
    .document('{colId}/{docId}/{subColId}/{subDocId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// add change the count
const runCounter = async function (change: any, context: any) {

    const col = context.params.colId;

    const eventsDoc = '_events';
    const countersDoc = '_counters';

    // ignore helper collections
    if (col.startsWith('_')) {
        return null;
    }
    // simplify event types
    const createDoc = change.after.exists && !change.before.exists;
    const updateDoc = change.before.exists && change.after.exists;

    if (updateDoc) {
        return null;
    }
    // check for sub collection
    const isSubCol = context.params.subDocId;

    const parentDoc = `${countersDoc}/${context.params.colId}`;
    const countDoc = isSubCol
        ? `${parentDoc}/${context.params.docId}/${context.params.subColId}`
        : `${parentDoc}`;

    // collection references
    const countRef = db.doc(countDoc);
    const countSnap = await countRef.get();

    // increment size if doc exists
    if (countSnap.exists) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.update(countRef, { count: i });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        const colRef = db.collection(change.after.ref.parent.path);
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(colRef);
            return t.set(countRef, { count: colSnap.size });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}

이벤트, 증분 및 트랜잭션을 처리합니다. 이것의 장점은 문서의 정확성에 대해 잘 모르는 경우 (아마도 베타 버전 일 때) 카운터를 삭제하여 다음 트리거에서 자동으로 추가되도록 할 수 있다는 것입니다. 예,이 비용이 발생하므로 다른 방법으로 삭제하지 마십시오.

카운트를 얻는 것과 같은 종류의 것 :

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');

또한 데이터베이스 저장 영역에서 비용을 절약하기 위해 오래된 이벤트를 제거하기위한 크론 작업 (스케줄 된 기능)을 작성할 수 있습니다. 최소한 계획이 필요하며 구성이 더있을 수 있습니다. 예를 들어 매주 일요일 오후 11시에 실행할 수 있습니다. https://firebase.google.com/docs/functions/schedule-functions

이것은 테스트되지 않았지만 몇 가지 조정으로 작동해야합니다.

exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *')
    .timeZone('America/New_York')
    .onRun(async (context) => {

        // get yesterday
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);

        const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
        const eventFilterSnap = await eventFilter.get();
        eventFilterSnap.forEach(async (doc: any) => {
            await doc.ref.delete();
        });
        return null;
    });

마지막으로 firestore.rules 의 컬렉션을 보호하는 것을 잊지 마십시오 .

match /_counters/{document} {
  allow read;
  allow write: if false;
}
match /_events/{document} {
  allow read, write: if false;
}

업데이트 : 쿼리

쿼리 수를 자동화하려는 경우 다른 답변에 추가하면 클라우드 기능 에서이 수정 된 코드를 사용할 수 있습니다.

    if (col === 'posts') {

        // counter reference - user doc ref
        const userRef = after ? after.userDoc : before.userDoc;
        // query reference
        const postsQuery = db.collection('posts').where('userDoc', "==", userRef);
        // add the count - postsCount on userDoc
        await addCount(change, context, postsQuery, userRef, 'postsCount');

    }
    return delEvents();

userDocument 에서 postsCount 를 자동으로 업데이트합니다 . 이 방법으로 다른 수를 다른 수에 쉽게 추가 할 수 있습니다. 이것은 단지 자동화하는 방법에 대한 아이디어를 제공합니다. 또한 이벤트를 삭제하는 또 다른 방법을 제공했습니다. 삭제하려면 각 날짜를 읽어야하므로 나중에 삭제하기 위해 실제로 저장하지 않고 기능을 느리게 만듭니다.

/**
 * Adds a counter to a doc
 * @param change - change ref
 * @param context - context ref
 * @param queryRef - the query ref to count
 * @param countRef - the counter document ref
 * @param countName - the name of the counter on the counter document
 */
const addCount = async function (change: any, context: any, 
  queryRef: any, countRef: any, countName: string) {

    // events collection
    const eventsDoc = '_events';

    // simplify event type
    const createDoc = change.after.exists && !change.before.exists;

    // doc references
    const countSnap = await countRef.get();

    // increment size if field exists
    if (countSnap.get(countName)) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.set(countRef, { [countName]: i }, { merge: true });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(queryRef);
            return t.set(countRef, { [countName]: colSnap.size }, { merge: true });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}
/**
 * Deletes events over a day old
 */
const delEvents = async function () {

    // get yesterday
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);

    const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
    const eventFilterSnap = await eventFilter.get();
    eventFilterSnap.forEach(async (doc: any) => {
        await doc.ref.delete();
    });
    return null;
}

또한 모든 onWrite 호출 기간마다 범용 기능이 실행될 것이라고 경고해야합니다. 특정 컬렉션의 onCreate 및 onDelete 인스턴스에서만 기능을 실행하는 것이 더 저렴할 수 있습니다. 우리가 사용하는 noSQL 데이터베이스와 마찬가지로 반복되는 코드와 데이터는 비용을 절약 할 수 있습니다.


쉽게 접근 할 수 있도록 매체에 기사를 쓰십시오.
— ahmadalibaloch


0

위의 답변 중 일부를 기반 으로이 작업을 수행하는 데 시간이 걸렸으므로 다른 사람들이 사용할 수 있다고 생각했습니다. 도움이 되길 바랍니다.

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {

    const categoryId = context.params.categoryId;
    const categoryRef = db.collection('library').doc(categoryId)
    let FieldValue = require('firebase-admin').firestore.FieldValue;

    if (!change.before.exists) {

        // new document created : add one to count
        categoryRef.update({numberOfDocs: FieldValue.increment(1)});
        console.log("%s numberOfDocs incremented by 1", categoryId);

    } else if (change.before.exists && change.after.exists) {

        // updating existing document : Do nothing

    } else if (!change.after.exists) {

        // deleting document : subtract one from count
        categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
        console.log("%s numberOfDocs decremented by 1", categoryId);

    }

    return 0;
});

0

나는 다른 접근법으로 많은 것을 시도했다. 마지막으로 방법 중 하나를 개선합니다. 먼저 별도의 컬렉션을 만들고 모든 이벤트를 저장해야합니다. 둘째, 시간별로 트리거 할 새 람다를 생성해야합니다. 이 람다는 이벤트 수집에서 이벤트를 계산하고 이벤트 문서를 지 웁니다. 기사의 코드 세부 사항. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca


블로그 게시물의 사람들이 실제로 StackOverflow의 요점을 가리 키지 않도록 관련 세부 사항과 코드 를 답변 자체 에 포함 시키십시오.
— DBS

0

이 쿼리는 문서 수를 산출합니다.

this.db.collection(doc).get().subscribe((data) => {
      count = data.docs.length;
    });

console.log(count)

1
매번 컬렉션에서 모든 문서를 가져 오므로 좋은 해결책이 아닙니다. 비용이 많이 듭니다. 더 좋은 방법은이 컬렉션에 새 문서가 추가 될 때마다 카운터를 설정하여 수천 개가 아닌 하나의 문서를 가져올 수 있습니다.
— Corentin Houdayer

0

이것은 계수를 사용하여 숫자 고유 ID를 작성합니다. 사용시 ID가 필요한 ID가 삭제 된 경우에도 감소하지 않습니다document .

collection고유 한 숫자 값이 필요한 작성 시

  1. appData하나의 문서 set로 .docID를 가진 컬렉션 을 지정하십시오.only
  2. uniqueNumericIDAmount에서 0으로 설정firebase firestore console
  3. 사용하여 doc.data().uniqueNumericIDAmount + 1고유 한 숫자 ID로
  4. 업데이트 appData모음 uniqueNumericIDAmount과 함께firebase.firestore.FieldValue.increment(1)
firebase
    .firestore()
    .collection("appData")
    .doc("only")
    .get()
    .then(doc => {
        var foo = doc.data();
        foo.id = doc.id;

        // your collection that needs a unique ID
        firebase
            .firestore()
            .collection("uniqueNumericIDs")
            .doc(user.uid)// user id in my case
            .set({// I use this in login, so this document doesn't
                  // exist yet, otherwise use update instead of set
                phone: this.state.phone,// whatever else you need
                uniqueNumericID: foo.uniqueNumericIDAmount + 1
            })
            .then(() => {

                // upon success of new ID, increment uniqueNumericIDAmount
                firebase
                    .firestore()
                    .collection("appData")
                    .doc("only")
                    .update({
                        uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
                            1
                        )
                    })
                    .catch(err => {
                        console.log(err);
                    });
            })
            .catch(err => {
                console.log(err);
            });
    });

-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });

1
이 답변은 코드 예제와 관련하여 더 많은 컨텍스트를 사용할 수 있습니다.
— ShellNinja
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.