Programing

Firebase Firestore 컬렉션 수

lottogame 2020. 8. 28. 07:46
반응형

Firebase Firestore 컬렉션 수


새로운 Firebase 데이터베이스 인 firestore를 사용하여 컬렉션에 포함 된 항목 수를 계산할 수 있나요?

그렇다면 어떻게해야합니까?



업데이트 (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 필드를 쿼리하여 컬렉션의 크기를 가져올 수 있습니다.


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

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

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

querySnapshot.docs.length;

또는 "querySnapshot"이 비어있는 경우 빈 값을 읽어 부울 값을 반환합니다.

querySnapshot.empty;

내가 아는 한 이것에 대한 빌트인 솔루션이 없으며 지금은 노드 SDK에서만 가능합니다. 당신이

db.collection ( 'someCollection')

당신이 사용할 수있는

.select ([필드])

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

예:

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

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

Cloud Firestore를 사용하여 컬렉션의 문서 수를 가져 오는 방법도 살펴보세요.


대규모 컬렉션 의 경우 문서 수를 신중하게 세십시오 . 모든 컬렉션에 대해 미리 계산 된 카운터를 갖고 싶다면 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;
});

그 이유는 모든 Cloud Firestore 트리거가 멱 등성을 가져야하기 때문입니다. Firestore 문서에는 https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees가 나와 있습니다.

해결책

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

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

As you can see, the key to prevent multiple execution is the property called eventId in the context object. If the function has been handled many times for the same event, the event id will be the same in all cases. Unfortunately, you must have "events" collection in your database.


I agree with @Matthew, it will cost a lot if you perform such query.

[ADVICE FOR DEVELOPERS BEFORE STARTING THEIR PROJECTS]

Since we have foreseen this situation at the beginning, we can actually make a collection namely counters with a document to store all the counters in a field with type number.

For example:

For each CRUD operation on the collection, update the counter document:

  1. When you create a new collection/subcollection: (+1 in the counter) [1 write operation]
  2. When you delete a collection/subcollection: (-1 in the counter) [1 write operation]
  3. When you update an existing collection/subcollection, do nothing on the counter document: (0)
  4. When you read an existing collection/subcollection, do nothing on the counter document: (0)

Next time, when you want to get the number of collection, you just need to query/point to the document field. [1 read operation]

In addition, you can store the collections name in an array, but this will be tricky, the condition of array in firebase is shown as below:

// 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'}

So, if you are not going to delete the collection , you can actually use array to store list of collections name instead of querying all the collection every time.

Hope it helps!


No, there is no built-in support for aggregation queries right now. However there are a few things you could do.

The first is documented here. You can use transactions or cloud functions to maintain aggregate information:

This example shows how to use a function to keep track of the number of ratings in a subcollection, as well as the average rating.

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

The solution that jbb mentioned is also useful if you only want to count documents infrequently. Make sure to use the select() statement to avoid downloading all of each document (that's a lot of bandwidth when you only need a count). select() is only available in the server SDKs for now so that solution won't work in a mobile app.


Increment a counter using 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),
    })
  );

In this example we increment an instanceCount field in the project each time a document is added to the instances sub collection. If the field doesn't exist yet it will be created and incremented to 1.

The incrementation is transactional internally but you should use a distributed counter if you need to increment more frequently than every 1 second.

It's often preferable to implement onCreate and onDelete rather than onWrite as you will call onWrite for updates which means you are spending more money on unnecessary function invocations (if you update the docs in your collection).


There is no direct option available. You cant't do db.collection("CollectionName").count(). Below are the two ways by which you can find the count of number of documents within a collection.

1 :- Get all the documents in the collection and then get it's size.(Not the best Solution)

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

By using above code your document reads will be equal to the size of documents within a collection and that is the reason why one must avoid using above solution.

2:- Create a separate document with in your collection which will store the count of number of documents in the collection.(Best Solution)

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

Above we created a document with name counts to store all the count information.You can update the count document in the following way:-

  • Create a firestore triggers on the document counts
  • Increment the count property of counts document when a new document is created.
  • Decrement the count property of counts document when a document is deleted.

w.r.t price (Document Read = 1) and fast data retrieval the above solution is good.


Took me a while to get this working based on some of the answers above, so I thought I'd share it for others to use. I hope it's useful.

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

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

            int Counter = documentSnapshots.size();

        }
    });

참고URL : https://stackoverflow.com/questions/46554091/firebase-firestore-collection-count

반응형