Firebase 云Firestore收集计数

Firebase 云Firestore收集计数,firebase,google-cloud-firestore,Firebase,Google Cloud Firestore,是否可以使用新的Firebase数据库Cloud Firestore计算集合中有多少项 如果是这样的话,我该怎么做?据我所知,目前没有内置解决方案,只能在node sdk中使用。 如果你有 db.collection('someCollection') 你可以用 .select([fields]) 定义要选择的字段。如果执行空select(),则只会得到一个文档引用数组 例如: db.collection('someCollection')。选择()。获取()。然后( (快照)=>conso

是否可以使用新的Firebase数据库Cloud Firestore计算集合中有多少项


如果是这样的话,我该怎么做?

据我所知,目前没有内置解决方案,只能在node sdk中使用。 如果你有

db.collection('someCollection')
你可以用

.select([fields])
定义要选择的字段。如果执行空select(),则只会得到一个文档引用数组

例如:

db.collection('someCollection')。选择()。获取()。然后(
(快照)=>console.log(snapshot.docs.length)
);

此解决方案仅针对下载所有文档的最坏情况进行了优化,不会扩展到大型集合

还可以看看这个:

否,目前没有对聚合查询的内置支持。然而,你可以做一些事情

第一个是。您可以使用事务或云功能来维护聚合信息:

此示例演示如何使用函数跟踪子集合中的评分数以及平均评分

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中可用,因此解决方案在移动应用程序中不起作用。

最简单的方法是读取“querySnapshot”的大小

您还可以读取“querySnapshot”中docs数组的长度

或者,如果通过读取空值“querySnapshot”为空,则返回布尔值

querySnapshot.empty;

正如许多问题一样,答案是——这要看情况而定

在前端处理大量数据时,应该非常小心。除了让前端感觉迟钝之外,您还可以制作Firestore。


小型收藏(少于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
})
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;
});
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;
});
前端:

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

大型收藏(1000多份文档) 最具扩展性的解决方案


FieldValue.increment()

这确保了即使同时从多个源进行更新(以前使用事务解决),我们也能获得正确的计数器值,同时还减少了我们执行的数据库读取次数


通过监听任何文档的删除或创建,我们可以添加或删除数据库中的计数字段

请参阅firestore文档- 或者看看杰夫·德莱尼的作品。对于任何使用AngularFire的人来说,他的指南都是非常棒的,但是他的课程也应该延续到其他框架中

云功能:

...
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
})
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;
});
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;
});
现在,在前端,您只需查询numberOfDocs字段即可获得集合的大小。

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

            int Counter = documentSnapshots.size();

        }
    });
@凌驾 public void OneEvent(QuerySnapshot文档快照,FireBaseFireStore异常e){ int Counter=documentSnapshots.size(); } });
我同意@Matthew的观点,如果您执行这样的查询,将花费大量的成本

[开始项目前给开发人员的建议]

由于我们在一开始就预见到了这种情况,我们实际上可以创建一个集合,即带有文档的计数器,以将所有计数器存储在类型为
number
的字段中

例如:

对于集合上的每个CRUD操作,更新计数器文档:

  • 创建新集合/子集合时:(+1在计数器中)[1写入操作]
  • 当您删除一个集合/子集合时:(-1在计数器中)[1写入操作]
  • 更新现有集合/子集合时,不对计数器文档执行任何操作:(0)
  • 阅读现有集合/子集合时,对计数器文档不执行任何操作:(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数据库有点复杂

    firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
    
                int Counter = documentSnapshots.size();
    
            }
        });
    
    这种代码在这种情况下不起作用:

    ...
    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
    })
    
    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;
    });
    
    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;
    });
    
    原因是,正如firestore文档所述,每个云firestore触发器都必须是幂等的:

    解决方案 因此,为了防止代码多次执行,您需要使用事件和事务进行管理。这是我处理大型收集计数器的特殊方式:

    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;
        });
    
    这里的用例