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 초마다 더 자주 증분해야하는 경우 .

그것은 구현하는 것이 바람직의 onCreateonDelete보다는 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.